The problem:
std::vector
and other containers have two functions for accessing / modifying their content: operator[]
and at()
.
at()
is meant for debugging, to catch out-of-bound bugs. However, at()
is also quite a lot slower than operator[]
. Both above facts would yearn for a way to switch between the use of at()
and operator[]
based on whether it is a debug build or a production build, especially if performance matters.
However, I can see no such easy way. Sadly standard containers don’t offer a flag or sth that would allow to do this.
I tried to write a simple wrapper that would switch between at()
and operator[]
based on whether or not NDEBUG
is defined, in line with how the assert
macro works.
Note: This is a different solution to the same problem as stated here: Allowing switching between operator[] and at() based on NDEBUG This solves the issue of 2d arrays. But I wanted to post two solutions, 'cuz I feared You could frown upon such a macro :(
The code:
// AOO = At Or Operator[]
#ifndef NDEBUG
#define AOO(INDEX) .at(INDEX)
#else
#define AOO(INDEX) [INDEX]
#endif
The use:
void testNormalUse()
{
std::vector<int> vec(100);
vec AOO(50) = 5;
std::cout << vec AOO(50) << std::endl;
}
void testRequireConst()
{
const std::vector<int> vec(100, 5);
std::cout << vec AOO(50) << std::endl;
}
void testRvalueRef()
{
std::vector<int>vec(100, 5);
std::cout << std::move(vec) AOO(50) << std::endl;
}
void test2d()
{
std::vector<std::vector<int>> vec(100, std::vector<int>(100, 5));
std::cout << vec AOO(50) AOO(50) << std::endl;
}
int main() {
testNormalUse();
testRequireConst();
testRvalueRef();
test2d();
}
Of course I know this is weird syntax. But the non-macro solution brings up pain with multidimensional arrays, a problem the macro version solves.
If only macros allowed punctuation, then we could make it look operator-like. But alas, they don't.
-
\$\begingroup\$ Possible duplicate of Allowing switching between `operator[]` and `at()` based on `NDEBUG` \$\endgroup\$πάντα ῥεῖ– πάντα ῥεῖ2017年03月17日 21:55:18 +00:00Commented Mar 17, 2017 at 21:55
-
\$\begingroup\$ @πάνταῥεῖ Is it forbidden on this site to post two substantially different solutions to same problem? \$\endgroup\$gaazkam– gaazkam2017年03月17日 21:57:55 +00:00Commented Mar 17, 2017 at 21:57
-
2\$\begingroup\$ Forbidden? No. But it's considered good practice to declare what you did and why to help the reviewers understand why you did what you did. The usual approach though, is posting both pieces of code in the same question and tag as comparative-review. \$\endgroup\$Mast– Mast ♦2017年03月18日 09:34:16 +00:00Commented Mar 18, 2017 at 9:34
1 Answer 1
I don't like the use of macro for this - particularly one that requires us to change calling code like this. I think it makes the code much less clear than when it has []
operators.
I would find it nicer to have an interposer class that can redirect []
to at()
, and that works for multi-argument indexers (since C++23):
#include <utility>
template<typename Container>
struct checked_if_debug : public Container
{
using Container::Container;
#ifndef NDEBUG
template<typename... Args>
auto&& operator[](this auto&& self, Args&&... args)
{
return self.at(std::forward<Args>(args)...);
}
#endif
};
That does change how we declare and construct our containers, though:
#include <iostream>
#include <stdexcept>
#include <vector>
void testNormalUse()
{
checked_if_debug<std::vector<int>> vec(100);
vec[50] = 5;
std::cout << vec[50] << '\n';
}
void testRequireConst()
{
const checked_if_debug<std::vector<int>> vec(100, 5);
std::cout << vec[50] << '\n';
}
void testRvalueRef()
{
checked_if_debug<std::vector<int>>vec(100, 5);
std::cout << std::move(vec)[50] << '\n';
}
void test2d()
{
checked_if_debug<std::vector<checked_if_debug<std::vector<int>>>> vec(100, checked_if_debug<std::vector<int>>(100, 5));
std::cout << vec[50][50] << '\n';
}
int main()
{
try {
testNormalUse();
testRequireConst();
testRvalueRef();
test2d();
} catch (const std::exception& e) {
std::cerr << e.what();
}
}
That's somewhat ameliorated if we use aliases, such as:
template<typename... Args>
using checked_vector = checked_if_debug<std::vector<Args...>>;
Then the worst-case is back to its initial complexity:
void test2d()
{
checked_vector<checked_vector<int>> vec(100, checked_vector<int>(100, 5));
std::cout << vec[50][50] << '\n';
}