Worth a thousand words:
#include<string> #include<iostream> class SayWhat { public: SayWhat& operator[](const std::string& s) { std::cout << s << "\n"; return *this; } }; int main() { SayWhat ohNo; // ohNo[1]; // Does not compile. Logic prevails. ohNo[0]; // you didn't! this compiles. return 0; }
The compiler is not complaining when passing the number 0 to the bracket operator accepting a string. Instead, this compiles and fails before entry to the method with:
terminate called after throwing an instance of 'std::logic_error' what(): basic_string::_S_construct null not valid
For reference:
> g++ -std=c++17 -O3 -Wall -Werror -pedantic test.cpp -o test && ./test > g++ --version gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)
My guess
The compiler is implicitly using the std::string(0)
constructor to enter the method, which yields the same problem (google the above error) for no good reason.
Question
Is there anyway to fix this on the class side, so the API user does not feel this and the error is detected at compile time?
That is, adding an overload
void operator[](size_t t) { throw std::runtime_error("don't"); }
is not a good solution.
Passing by ref (string &n) requires that the called data be changeable. Using const string &n removes the requirement that it can be changed - as it's specified const. string n works because this is passing-by-value so that n is a copy of the passed data - and any changes made would just affect the copy.
The data type const string& literally means “a reference to a string object whose contents will not be changed.” 1. Pass by value - a copy of the original object is created and passed. To pass a string by value, you just use the data type string.
Pass String by Reference in C++ The C++ reference is a name for a variable that already exists. A reference to a variable can't be altered to refer to the other variable once initialized. Pointers or references can be passed as parameters to functions in C++.
The reason std::string(0)
is valid, is due to 0
being a null pointer constant. So 0 matches the string constructor taking a pointer. Then the code runs afoul of the precondition that one may not pass a null pointer to std::string
.
Only literal 0
would be interpreted as a null pointer constant, if it was a run time value in an int
you wouldn't have this problem (because then overload resolution would be looking for an int
conversion instead). Nor is literal 1
a problem, because 1
is not a null pointer constant.
Since it's a compile time problem (literal invalid values) you can catch it at compile time. Add an overload of this form:
void operator[](std::nullptr_t) = delete;
std::nullptr_t
is the type of nullptr
. And it will match any null pointer constant, be it 0
, 0ULL
, or nullptr
. And since the function is deleted, it will cause a compile time error during overload resolution.
One option is to declare a private
overload of operator[]()
that accepts an integral argument, and don't define it.
This option will work with all C++ standards (1998 on), unlike options like void operator[](std::nullptr_t) = delete
which are valid from C++11.
Making the operator[]()
a private
member will cause a diagnosable error on your example ohNo[0]
, unless that expression is used by a member function or friend
of the class.
If that expression is used from a member function or friend
of the class, the code will compile but - since the function is not defined - generally the build will fail (e.g. a linker error due to an undefined function).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With