Search code examples
c++stringstdimplicit-conversion

Prevent function taking const std::string& from accepting 0


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.


Solution

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