Prevent function taking const std::string& from accepting 0
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).
Using string_view helps (somewhat)
As of C++17, we have the std::string_view
class. It is intended exactly for this use-case, of passing non-owning references-to-string-like-objects, to functions which only read a string. You should seriously consider using it for this kind of operators.
Now, std:: string_view
has its own set issues (See: enough string_view
to hang ourselves with), but here it will give you a useful warning. If you replace:
SayWhat& operator[](const std::string& s) {
with
SayWhat& operator[](std::string_view s) {
and you compile with --std=c++17 -Wall
, you get:
<source>: In function 'int main()':
<source>:16:11: warning: null argument where non-null required (argument 2) [-Wnonnull]
16 | ohNo[0]; // you didn't! this compiles.
| ^