我有这个代码:
class MyString { public: operator const char*() const { return nullptr; } }; class YourString { public: YourString() {} YourString(const char* ptr) { (void)ptr; } YourString& operator=(const char* ptr) { return *this; } }; int main() { MyString mys; YourString yoursWorks; yoursWorks = mys; YourString yoursAlsoWorks(mys); YourString yoursBreaks = mys; }
MSVC毫无疑问地接受它.Clang-CL不接受它:
$ "C:\Program Files\LLVM\msbuild-bin\CL.exe" ..\string_conversion.cpp ..\string_conversion.cpp(32,13): error: no viable conversion from 'MyString' to 'YourString' YourString yoursBreaks = mys; ^ ~~~ ..\string_conversion.cpp(10,7): note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'MyString' to 'const YourString &' for 1st argument class YourString ^ ..\string_conversion.cpp(10,7): note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'MyString' to 'YourString &&' for 1st argument class YourString ^ ..\string_conversion.cpp(14,2): note: candidate constructor not viable: no known conversion from 'MyString' to 'const char *' for 1st argument YourString(const char* ptr) { ^ ..\string_conversion.cpp(5,2): note: candidate function operator const char*() const { ^ 1 error generated.
GCC也不是:
$ g++.exe -std=gnu++14 ..\string_conversion.cpp ..\string_conversion.cpp: In function 'int main()': ..\string_conversion.cpp:33:27: error: conversion from 'MyString' to non-scalar type 'YourString' requested YourString yoursBreaks = mys; ^
我知道只允许一个用户定义的转换.
然而,MSVC是否有理由对待该生产线
YourString yoursBreaks = mys;
如
YourString yoursBreaks(mys);
并接受它?转换编译器是允许的吗?根据什么规则允许/不允许?有类似的规则吗?
更新:使用MSVC时,/Za
标志会导致代码无法被接受.
$ "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\x86_amd64\CL.exe" /Za ..\string_conversion.cpp string_conversion.cpp ..\string_conversion.cpp(33): error C2440: 'initializing': cannot convert from 'MyString' to 'YourString' ..\string_conversion.cpp(33): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
Barry.. 10
tldr; 代码格式不正确,MSVC接受它是错误的.复制初始化与直接初始化不同.外行解释是初始化yoursBreaks
将涉及两个用户定义的转换(MyString --> const char* --> YourString
),而直接初始化涉及一个用户定义的转换(MyString --> const char*
),并且最多允许一个用户定义的转换.强制执行该规则的标准解释是[over.best.ics]不允许在通过转换构造函数从不相关的类类型复制初始化类类型的上下文中进行用户定义的转换.
达到标准!什么:
YourString yoursBreaks = mys;
意思?每当我们声明一个变量时,那就是某种初始化.在这种情况下,根据[dcl.init]:
初始化
=
以大括号或等于初始值或条件(6.4)的形式发生,以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.6.1),称为复制初始化.
复制初始化是形式的任何东西T var = expr;
尽管它的外观=
,这永远不会调用operator=
.我们总是经历构造函数或转换函数.
具体来说,这种情况:
如果目标类型是(可能是cv限定的)类类型:
- 如果初始化表达式是prvalue,并且源类型的cv-nonqualified版本与目标类相同,[...]
-否则,如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类的类相同,或者是派生类,[...]
- 否则(即,对于剩余的复制初始化情况),可以按照13.3中的描述枚举可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列. .1.4,通过重载决议(13.3)选择最好的一个.如果转换不能完成或不明确,则初始化是错误的.
我们陷入了最后一颗子弹.让我们跳到13.3.1.4:
- T的转换构造函数(12.3.1)是候选函数.
- 当初始化表达式的类型是类类型" cvS
"时,将S
考虑其非基本类的非显式转换函数.初始化临时绑定到构造函数的第一个参数时,其中参数的类型为"引用可能的cv -qualifiedT
",并且在直接初始化类型为"对象"的上下文中使用单个参数调用构造函数cv2T
",也考虑了显式转换函数.那些未隐藏在其中S
并且产生其cv-nonqualified版本与其类型相同T
或者是其派生类的类型的候选函数.返回"引用X
"的转换函数返回左值或x值,具体取决于类型的引用类型X
,因此被认为X
是为此选择候选函数的过程产生的.
第一个要点为我们提供了转换构造函数YourString
,它们是:
YourString(const char* );
第二颗子弹给了我们什么.MyString
没有返回的转换函数YourString
或从中派生的类类型.
好吧,好吧.我们有一个候选构造函数.它可行吗?[over.match]通过以下方式检查可靠性:
然后,基于将每个参数与每个可行函数的相应参数匹配所需的隐式转换序列(13.3.3.1)来选择最佳可行函数.
并且,在[over.best.ics]中:
格式良好的隐式转换序列是以下形式之一:
- 标准转换序列(13.3.3.1.1),
- 用户定义的转换序列(13.3.3.1.2),或
- 省略号转换序列(13.3) .3.1.3).但是,如果目标是
- 构造函数的第一个参数或
- 用户定义的转换函数的隐式对象参数并且构造函数或用户定义的转换函数是
-13.3.1.3 的候选者,当参数是类复制初始化的第二步中的临时函数时,
- 13.3.1.4,13.3.1.5或13.3.1.6(in所有情况),或
-
不考虑 13.3.1.7 [...] 用户定义的转换序列的第二阶段.[注意:这些规则阻止在重载解析期间应用多个用户定义的转换,从而避免无限递归.- 尾注] [示例:struct Y { Y(int); }; struct A { operator int(); }; Y y1 = A(); // error: A::operator int() is not a candidate struct X { }; struct B { operator X(); }; B b; X x({b}); // error: B::operator X() is not a candidate- 末端的例子]
因此,即使有从转换序列MyString
来const char*
,它不是在这种情况下考虑的,所以该构造是不可行的.
由于我们没有另一个候选构造函数,因此调用格式不正确.
另一行:
YourString yoursAlsoWorks(mys);
被称为直接初始化.我们在前面引用的[dcl.init]块中调用了三个子弹点,它的全部内容如下:
列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.
其中13.3.1.3表示构造函数是从以下枚举的:
对于不在复制初始化上下文中的直接初始化或默认初始化,候选函数是要初始化的对象的类的所有构造函数.
那些构造函数是:
YourString(const char* ) // yours YourString(YourString const& ) // implicit YourString(YourString&& ) // implicit
为了检查后两个函数的可行性,我们从复制初始化上下文(根据上面的内容失败)重新执行重载解析.要不是你YourString(const char*)
,这是简单的,有一个可行的转换功能,从MyString
到const char*
,所以它的使用.
请注意,这里只有一个转换:MyString --> const char*
.一次转换很好.
tldr; 代码格式不正确,MSVC接受它是错误的.复制初始化与直接初始化不同.外行解释是初始化yoursBreaks
将涉及两个用户定义的转换(MyString --> const char* --> YourString
),而直接初始化涉及一个用户定义的转换(MyString --> const char*
),并且最多允许一个用户定义的转换.强制执行该规则的标准解释是[over.best.ics]不允许在通过转换构造函数从不相关的类类型复制初始化类类型的上下文中进行用户定义的转换.
达到标准!什么:
YourString yoursBreaks = mys;
意思?每当我们声明一个变量时,那就是某种初始化.在这种情况下,根据[dcl.init]:
初始化
=
以大括号或等于初始值或条件(6.4)的形式发生,以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.6.1),称为复制初始化.
复制初始化是形式的任何东西T var = expr;
尽管它的外观=
,这永远不会调用operator=
.我们总是经历构造函数或转换函数.
具体来说,这种情况:
如果目标类型是(可能是cv限定的)类类型:
- 如果初始化表达式是prvalue,并且源类型的cv-nonqualified版本与目标类相同,[...]
-否则,如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类的类相同,或者是派生类,[...]
- 否则(即,对于剩余的复制初始化情况),可以按照13.3中的描述枚举可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列. .1.4,通过重载决议(13.3)选择最好的一个.如果转换不能完成或不明确,则初始化是错误的.
我们陷入了最后一颗子弹.让我们跳到13.3.1.4:
- T的转换构造函数(12.3.1)是候选函数.
- 当初始化表达式的类型是类类型" cvS
"时,将S
考虑其非基本类的非显式转换函数.初始化临时绑定到构造函数的第一个参数时,其中参数的类型为"引用可能的cv -qualifiedT
",并且在直接初始化类型为"对象"的上下文中使用单个参数调用构造函数cv2T
",也考虑了显式转换函数.那些未隐藏在其中S
并且产生其cv-nonqualified版本与其类型相同T
或者是其派生类的类型的候选函数.返回"引用X
"的转换函数返回左值或x值,具体取决于类型的引用类型X
,因此被认为X
是为此选择候选函数的过程产生的.
第一个要点为我们提供了转换构造函数YourString
,它们是:
YourString(const char* );
第二颗子弹给了我们什么.MyString
没有返回的转换函数YourString
或从中派生的类类型.
好吧,好吧.我们有一个候选构造函数.它可行吗?[over.match]通过以下方式检查可靠性:
然后,基于将每个参数与每个可行函数的相应参数匹配所需的隐式转换序列(13.3.3.1)来选择最佳可行函数.
并且,在[over.best.ics]中:
格式良好的隐式转换序列是以下形式之一:
- 标准转换序列(13.3.3.1.1),
- 用户定义的转换序列(13.3.3.1.2),或
- 省略号转换序列(13.3) .3.1.3).但是,如果目标是
- 构造函数的第一个参数或
- 用户定义的转换函数的隐式对象参数并且构造函数或用户定义的转换函数是
-13.3.1.3 的候选者,当参数是类复制初始化的第二步中的临时函数时,
- 13.3.1.4,13.3.1.5或13.3.1.6(in所有情况),或
-
不考虑 13.3.1.7 [...] 用户定义的转换序列的第二阶段.[注意:这些规则阻止在重载解析期间应用多个用户定义的转换,从而避免无限递归.- 尾注] [示例:struct Y { Y(int); }; struct A { operator int(); }; Y y1 = A(); // error: A::operator int() is not a candidate struct X { }; struct B { operator X(); }; B b; X x({b}); // error: B::operator X() is not a candidate- 末端的例子]
因此,即使有从转换序列MyString
来const char*
,它不是在这种情况下考虑的,所以该构造是不可行的.
由于我们没有另一个候选构造函数,因此调用格式不正确.
另一行:
YourString yoursAlsoWorks(mys);
被称为直接初始化.我们在前面引用的[dcl.init]块中调用了三个子弹点,它的全部内容如下:
列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.
其中13.3.1.3表示构造函数是从以下枚举的:
对于不在复制初始化上下文中的直接初始化或默认初始化,候选函数是要初始化的对象的类的所有构造函数.
那些构造函数是:
YourString(const char* ) // yours YourString(YourString const& ) // implicit YourString(YourString&& ) // implicit
为了检查后两个函数的可行性,我们从复制初始化上下文(根据上面的内容失败)重新执行重载解析.要不是你YourString(const char*)
,这是简单的,有一个可行的转换功能,从MyString
到const char*
,所以它的使用.
请注意,这里只有一个转换:MyString --> const char*
.一次转换很好.