C++11隐式类型转换

8
#include <string>

struct String
{
    template<typename T> operator T*() { return 0; }
    operator std::string() { return ""; }
};

int main()
{
    String myStr;

    std::string str1(myStr); // ambiguous, error C2668

    std::string str2 = myStr; // error C2440:
    // 'initializing' : cannot convert from 'String' to
    // `std::basic_string<char,std::char_traits<char>,std::allocator<char>>',
    // No constructor could take the source type,
    // or constructor overload resolution was ambiguous

    const std::string& rStr = myStr; // Ok, but why?
}

我正在使用VS 2013。

问题:

  1. 为什么str1str2的定义会导致不同的编译错误?

  2. 据我所知,当创建rStr时,首先会创建一个临时字符串对象,然后rStr将引用该临时对象。但是,为什么创建临时对象不会导致编译错误?tmpstrN之间是否有任何区别?


据我所知,第二个错误是一个bug。g++和clang++接受str2的定义和初始化。 - dyp
1个回答

8
第一个定义,std::string str1(myStr); 确实是有歧义的:
std::string str1(myStr.operator char*());
// or
std::string str1(myStr.operator std::string());

所以,由于存在歧义,这个初始化失败了。

本质上,这种情况与...

void foo(char const*);
void foo(std::string);

foo(myStr); // ambiguous

需要一个自定义转换,然后将调用一个函数(对于第一种定义,这个函数是构造函数)。两种转换都可行,并且没有任何一种是另一种的子集,因此两种具有相同的等级。


第二个定义std::string str2 = myStr;实际上是可以的。只允许一种自定义转换到std::string,可以通过构造函数或转换函数实现,但不可以同时使用两者。因此,只有std::string str2 = myStr.operator std::string();是可行的。

请注意,当expr不是string类型时,string str2 = expr;需要将expr 转换为std::string。然后使用结果临时变量通过复制/移动来初始化str2

string str2 = string(expr);
//            ~~~~~~ implicit

因此,右侧的转换 必须直接转换std::string,否则你需要一系列两个用户定义的转换来初始化临时变量:(UDC = 用户定义的转换)
string str2 = string(expr);
// resolved as:
string str2 = expr.operator string();        // fine: one implicit UDC
string str2 = string(expr.operator char*()); // error: two UDCs

例如,将表达式 expr 经过 operator char* 转换为 char const*,然后经过转换构造函数再转换为 std::string 需要两个用户定义的转换链,因此不可行。如果我们尝试使用 operator char*() 转换,则需要进行额外的隐式构造函数调用,将右侧操作数变为 string
这与 string str1( expr ) 不同,其中 expr 不需要被隐式转换为 string。它可能需要被转换来初始化字符串构造函数的参数。从 可能转换过的 expr 直接初始化 str1 本身并不是隐式转换,而只是一个函数调用。不会创建额外的临时对象:
string str1( expr );
// resolved as:
string str1( expr.operator string() ); // fine
string str1( expr.operator char* () ); // fine

当启用语言扩展编译时,第二个定义会被拒绝。在没有语言扩展的情况下,在VS2013 Update 2中进行此初始化是可以的。


第三个定义遵循不同的初始化方案。据我所知,在这种情况下它应该像第二个定义一样运行。看起来语言扩展只适用于第二个定义,而不适用于第三个定义。


也许扩展的行为与此行为有关,即在分配给std::string时(http://connect.microsoft.com/VisualStudio/feedback/details/743685/std-string-assignment-should-probably-be-ambiguous-and-fail-to-compile-but-is-not)。 - dyp
第二个定义,是否有这样的东西,std::string str2(myStr->operator char*) 或者 std::string str2(myStr->operator std::string); - Leonhart Squall
@LeonhartSquall 抱歉,我不太明白你的意思。我用可能更少歧义的函数调用替换了我用来显示哪些转换被执行的松散语法。第二个定义在功能上等同于 std::string str2( myStr.operator std::string() );。也就是说,一个临时的 std::string 被构造为转换函数的返回值;然后将此临时对象移动到 str2 中。 - dyp
据我所知,编译器一次只会应用一个用户定义的转换。在第一种情况下,在初始化中,转换为变量类型的初始化程序是隐式转换。因此,对于std::string str1(myStr.operator char*()),为什么我不能认为会导致两次隐式转换?因此,只有std::string str1(myStr.operator std::string())是正确的,在第一种情况下。我确切地不理解std::string str1(myStr)和std::string str2 = myStr之间的区别,我认为它们都是初始化程序。 - Leonhart Squall
显式构造不是类型转换。类型转换包括构造一个临时变量。你同样可以有一个重载函数集,例如 void foo(char const*); void foo(std::string); foo(myStr);,这也会出现同样的问题。对于第二个问题,需要注意 =() 的初始化行为是不同的。使用括号是直接初始化(将直接调用构造函数),而 =复制初始化(将创建一个临时变量并进行复制/移动)。 - dyp
显示剩余2条评论

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