由于转换错误而导致的过载/专业不正确

Incorrect overload/specialization due to wrong conversion

本文关键字:不正确 于转换 转换 错误      更新时间:2023-10-16

在我自己的代码中一个错误之后,(在我看来)编译器选择了错误的过载,我一直在挖掘解释,但找不到简单的说明。我确实找到了Herb Sutter的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的选择。我仅使用一个转换构造函数测试了类似版本。这些也不起作用。

问题:

  1. 为什么编译器尝试调用字符串题词版本?
  2. 为什么bar()呼叫Matter中的<Foo>的添加剂。
  3. 我如何防止这种情况发生。例如。当输入Bool时,我可以强迫编译器选择bar( const Foo& )吗?
  4. 另外,有人打电话给bar< Foo >( false )时我可以执行编译吗?

这是四个问题的答案:

  1. 为什么编译器尝试调用字符串题词版本?您的Foo类有两个构造函数,采用bool,这是模棱两可的,因此不考虑将bool转换为Foo(如果要检测到是否传递了临时或LVALUE,则可以使用bool&&bool const&在C 中超载类型)。std::string可以从char const*构造,并且false可以升级为null指针常数。无效的指针常数在语法上是有效的char const*,但它是非法值,并传递会导致不确定的行为。
  2. 为什么bar()呼叫中的添加剂很重要?指定模板参数时,您会抑制模板参数扣除,并告诉编译器要使用哪个参数。明确指定模板参数是可以选择bar()的过载std::string的唯一方法,因为该模板无法推导T
  3. 我如何防止这种情况发生。例如。当Bool输入时,我可以强迫编译器选择栏(const foo&amp;)吗?最简单的方法是让编译器推论模板参数:bar() S的版本从未由编译器自动选择,因为编译器无法推断模板参数。另外,如果要明确指定模板参数,并且只有bool是一个问题,则可以添加一个Overload服用bool并制作推论版本,并将bool版本委托给相同的内部功能。
  4. 另外,当有人打电话给bar&let时,我可以执行编译错误;foo>(false)?通过删除过载,使用C 2011这很容易(请参阅下文)。

使用bool与明确指定的模板参数创建编译时错误,您可以添加此过载:

template <typename T> void bar(bool) = delete;

删除功能可在C 2011中获得。

主要问题似乎是:如果Foostd::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::stringFoo版本之间的歧义。

第一个示例, bar( false );呼叫 template<typename T> void bar( const T& value ),因为那是一个精确的与t = bool匹配。

当您指定t = foo时,两个过载都不是确切的匹配,因此您可以涉及相当复杂的规则,以了解应用哪些隐式转换。大多数C 程序员都不完全了解这些,因此最好避免避免隐性转换。

在这种情况下,最简单的修复方法就是为布尔添加另一个过载。

template<typename T>
void bar( bool )
{
    std::cerr << "bool" << std::endl;
}

然后,在该超载中,您可以明确应用转换并调用所需的版本。

此行

bar< Foo >( false );

可以匹配这两个功能中的任何一个:

void bar<Foo>(const Foo&);
void bar<Foo>(const std::string&);

现在,如果两个UDT都有bool的隐式转换构造函数,那么它应该选择哪一个?它们都有相同的优先级,因为它们均相同类型的转换。

至少...大概这就是正在发生的事情,尽管它确实不应该选择促销 构造而不是仅构造序列。