(私の意見では) 間違ったオーバーロードがコンパイラによって選択されたという自分のコードのバグの後、説明を探していましたが、簡単な説明を見つけることができませんでした。専門化の問題を扱っているHerb Sutter の GOTW 49を見つけました。また、stackoverflow に関するいくつかの質問を見つけましたが、原因を実際に説明したり、適切な解決策を提供したりすることはできませんでした。
ブール値から構築できる単一のクラス Foo があります。std::string は bool (false) からも構築できることがわかりました (難しい方法です)。
以下に示すように、引数が異なる 3 つの (テンプレート) メソッドがあります。1 つのメソッドは「任意の」テンプレート引数を受け入れ、2 つの特殊化は構造体 Foo を受け入れ、別のメソッドは文字列を受け入れます。
#include <string>
#include <iostream>
struct Foo
{
Foo() : value( false ){ };
Foo( bool v ) : value ( v ) { }
Foo( const bool& v ) : value( v ) { }
bool value;
};
template< typename T >
void bar( const T& value )
{
std::cerr << "template bar" << std::endl;
}
template< >
void bar< Foo >( const Foo& )
{
std::cerr << "template bar with Foo" << std::endl;
}
template< typename T >
void bar( const std::string& )
{
std::cerr << "template bar with string" << std::endl;
}
int main( int argc, char* argv[] )
{
bar( false ); // Succeeds and calls 1st bar( const T& )
bar< Foo >( false ); // Crashes, because 2nd bar( const std::string& )
// is called with false promoted to null pointer.
return 0;
}
これを Visual Studio 2010 と MinGW (gcc 4.7.0) でテストしました。GCC は適切にコンパイル警告を出しますが、msvc は出ません:
main.cpp:34:20: warning: converting 'false' to pointer type for argument 1 of 'std::basic_string< ... ' [-Wconversion-null]
小さな更新 (コード内): Foo を使用した明示的な特殊化でも機能しません。\
小さな更新 2: コンパイラは、「あいまいなオーバーロード」について文句を言いません。
bool
小さな更新 3: 一部の人々は、Foo の 2 つのコンストラクターが を受け入れると、Foo の選択を「無効にする」と答えています。単一の変換コンストラクターのみを使用して、同様のバージョンをテストしました。これらも機能しません。
質問:
- コンパイラが文字列引数バージョンを呼び出そうとするのはなぜですか?
- そして、なぜ
<Foo>
bar() 呼び出しでの additon が重要なのでしょうか。 - どうすればこれを防ぐことができますか。
bar( const Foo& )
たとえば、bool が入力されたときにコンパイラに強制的に選択させることはできますか? - または、誰かが呼び出したときにコンパイルエラーを強制できます
bar< Foo >( false )
か?