web-dev-qa-db-ja.com

const std :: string&を取る関数が0を受け入れないようにする

千の言葉に値する:

_#include<string>
#include<iostream>

class SayWhat {
    public:
    SayWhat& operator[](const std::string& s) {
        std::cout<<"here\n"; // To make sure we fail on function entry
        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");
}
_

良い解決策ではありません。

97
kabanus

std::string(0)が有効な理由は、0がNULLポインター定数であるためです。したがって、0は、ポインターを取る文字列コンストラクターと一致します。次に、コードは、std::stringにNULLポインターを渡さないという前提条件に反して実行されます。

リテラル0のみがNULLポインター定数として解釈されます。これがintのランタイム値である場合、この問題は発生しません(オーバーロード解決ではint変換が検索されるため)。 1はNULLポインター定数ではないため、リテラル1も問題ではありません。

コンパイル時の問題(リテラルの無効な値)なので、コンパイル時にキャッチできます。このフォームのオーバーロードを追加します。

void operator[](std::nullptr_t) = delete;

std::nullptr_tnullptrのタイプです。そして、00ULL、またはnullptrのいずれかであるany nullポインター定数に一致します。また、関数が削除されるため、オーバーロード解決中にコンパイル時エラーが発生します。

1つのオプションは、整数引数を受け入れるoperator[]()privateオーバーロードを宣言し、それを定義しないことです。

このオプションは、C++ 11で有効なvoid operator[](std::nullptr_t) = deleteなどのオプションとは異なり、すべてのC++標準(1998以降)で機能します。

operator[]()privateメンバーにすると、サンプルで診断可能なエラーが発生しますohNo[0](その式がメンバー関数またはクラスのfriendで使用されている場合を除く).

その式がメンバー関数またはクラスのfriendから使用される場合、コードはコンパイルされますが、関数は定義されていないため、一般にビルドは失敗します(たとえば、未定義の関数によるリンカーエラー)。

26
Peter