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.
      |           ^