防止函数接受 const std: : string 和接受0

千言万语:

#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;
}

当编译器将数字0传递给接受字符串的括号运算符时,编译器不会抱怨。相反,在输入方法之前,这将编译并失败,具有:

terminate called after throwing an instance of 'std::logic_error'
what():  basic_string::_S_construct null not valid

参考资料:

> 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)

我猜的

编译器隐式地使用 std::string(0)构造函数来输入该方法,这会毫无理由地产生相同的问题(google 上面的错误)。

提问

在类端是否有办法修复这个问题,这样 API 用户就不会感觉到这个问题,并且在编译时检测到错误?

也就是说,增加一个过载

void operator[](size_t t) {
throw std::runtime_error("don't");
}

不是个好办法。

4695 次浏览

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