在我自己的代码中出现错误之后,(在我看来)编译器选择了错误的重载,我一直在寻找解释,但找不到一个简单的错误。我找到Herb Sutter's GOTW 49来处理专业化问题。我还发现了一些关于stackoverflow的问题,但没有一个能真正解释我的原因,也没有为我提供好的解决方案。
我有一个单独的类Foo,它可以用布尔值构造。我发现(艰难的方式)std :: string也可以用bool(false)构建。
我有三个(模板)方法,具有不同的参数,如下所示。一个方法接受“任何”模板参数和两个特化,接受一个struct 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:编译器不会抱怨“模糊过载”。
小更新3:有些人回答Foo的两个构造函数接受bool
,“使Foo的选择无效”。我只用一个转换构造函数测试了类似的版本。这些也不起作用。
问题:
<Foo>
的附加值很重要。bar( const Foo& )
吗? bar< Foo >( false )
?答案 0 :(得分:4)
以下是四个问题的答案:
Foo
有两个带有bool
的构造函数,这些构造函数不明确,因此,不会考虑将bool
转换为Foo
(如果您想要检测临时或是传入左值,您可以使用bool&&
和bool const&
作为参数类型在C ++中重载。 std::string
可以从char const*
构造,false
可以提升为空指针常量。空指针常量在语法上是有效的char const*
,但它是非法值并且传递它会导致未定义的行为。bar()
加载std::string
的重载的方法,因为无法推断出此模板T
。bar()
的版本,因为编译器无法推断出模板参数。或者,如果要明确指定模板参数并且只有bool
是一个问题,则可以添加一个重载bool
并将推导的版本和bool
版本委托给同一个内部功能。在将bool
与明确指定的模板参数一起使用时创建编译时错误,可以添加此重载:
template <typename T> void bar(bool) = delete;
C ++ 2011中提供了删除功能。
主要问题似乎是:如果Foo
和std::string
都可以从bool
转换,那么为什么std::string
选择bar<Foo>(bool)
调用和以下重载可用吗?
template <typename T> void bar(T const&);
template <> void bar<Foo>(Foo const&);
template <typename T> void bar(std::string const&);
首先,重载决策选择主模板,忽略任何特化)。由于bar(std::string const&)
是比推断模板参数的版本更专业的接口,因此选择此版本。在此阶段忽略第一个模板的特化。要使用Foo
也适用,您可以添加
template <typename T> void bar(Foo const&);
并且在bar<Foo>(false)
和std::string
版本之间调用Foo
会有歧义。
答案 1 :(得分:2)
第一个示例bar( false );
调用template<typename T> void bar( const T& value )
,因为这与T = bool完全匹配。
当你指定T = Foo时,两个重载都不再是完全匹配,所以你会进入相当复杂的规则,关于应用哪些隐式转换。大多数C ++程序员并不完全理解这些,所以你最好避免隐式转换。
在这种情况下最简单的解决方法是为bool添加另一个重载。
template<typename T>
void bar( bool )
{
std::cerr << "bool" << std::endl;
}
然后在该重载中,您可以显式应用转换并调用所需的版本。
答案 2 :(得分:1)
这一行
bar< Foo >( false );
可以匹配这两个函数中的任何一个:
void bar<Foo>(const Foo&);
void bar<Foo>(const std::string&);
现在,如果两个UDT都有来自bool
的隐式转换构造函数,那么它应该选择哪一个?它们都具有相同的优先级,因为它们都是相同类型的转换。
至少......大概是正在发生的事情,虽然它真的不应该选择促销+建筑而不仅仅是建筑序列。