显式转换、直接初始化和复制初始化之间的不同行为

7
我有一个类C,其中有一个转换运算符可以转换为任何类型。在这个例子中,我尝试以三种不同的方式将其实例转换为std :: string: static_cast , std :: string 的构造函数和赋值给 std :: string 。然而,只有最后一种编译,而其他方法会引发模糊的构造函数错误。
错误的原因很清楚:有很多方法可以将C转换为std :: string的构造函数可以接受的内容。但是这些情况之间有什么区别?为什么转换运算符在这里起作用但在其他地方却不起作用?
struct C {
    template<typename T>
    operator T() const {
        return T{};
    }
};

int main() {
    C c;

    cout << static_cast<string>(c) << endl; // compile error
    string bad(c); // compile error
    string ok = c; // compiles successfully
}

更新:正如bolov在评论中提到的那样,使用C++17不会出现这个问题。我测试了使用-std=c++11和-std=c++14的g++-5和clang-3.8,结果显示出了描述的错误。


无法复现:https://godbolt.org/g/ESR8cw - bolov
@bolov 很奇怪,但在 C++17 中它无法重现。在 godbolt 上使用 -std=c++11 可以重现。我会将其添加到帖子中,谢谢。 - Ivan Smirnov
嗯...有趣。 - bolov
@bolov 在我的gcc/clang上无法编译,例如在这里(http://coliru.stacked-crooked.com/a/98b71e48f758054a)。确实,g++ -std=c++17可以接受这段代码。 - vsoftco
@vsoftco 不行。如果你也加上 operator const char*,歧义就会再次出现。现在没有模板了。 - Ivan Smirnov
@IvanSmirnov 我修改了标题,因为 string ok = c; 不是赋值,而是复制初始化;其初始值设定项包含等号。 - songyuanyao
1个回答

7

C++17之前

static_cast<string>(c)string bad(c) 执行直接初始化,然后

检查T的构造函数并通过重载解析选择最佳匹配项。然后调用构造函数来初始化对象。

就像你所说的,所有可能的std::string构造函数都会被检查,并且C可以转换为任何需要的类型,这样就会导致二义性。

string ok = c执行复制初始化(注意不是赋值),然后

如果T是一个类类型,而other的非cv限定版本不是T或从T派生出来的,或者如果T是非类类型,但other的类型是类类型,则将搜索用户定义的转换序列,以便从other的类型转换到T(如果T是类类型且可用转换函数进行转换,则转换为派生类型)。然后通过重载解析选择最佳的转换序列。

这意味着会检查从Cstd::string的转换,并用于此处的初始化。

C++17之后

自C++17以来,对于直接初始化

如果初始化程序是一个cv限定的prvalue表达式,其类型与T相同,则使用初始化表达式本身而非由其产生的临时对象来初始化目标对象:请参见复制省略(自C++17起)

这意味着将优先使用从Cstd::string的转换进行初始化,然后二义性消失,并且代码正常工作。

LIVE


1
进一步阐述复制初始化([over.ics.user] / 3):如果用户定义的转换是由转换函数模板的特化指定的,则第二个标准转换序列必须具有精确匹配等级。我不会说这是首选,而是必需的。 - chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接