[英]Why explicit-ness of std::pair's heterogeneous "move"-constructor changed in C++17?
[英]C++17: explicit conversion function vs explicit constructor + implicit conversions - have the rules changed?
Clang 6,clang 7和gcc 7.1,7.2和7.3都同意以下是有效的C ++ 17代码,但在C ++ 14和C ++ 11下是不明确的。 MSVC 2015和2017也接受它。 但是,即使在c ++ 17模式下,gcc-8.1和8.2也拒绝它:
struct Foo
{
explicit Foo(int ptr);
};
template<class T>
struct Bar
{
operator T() const;
template<typename T2>
explicit operator T2() const;
};
Foo foo(Bar<char> x)
{
return (Foo)x;
}
接受它的编译器选择模板显式转换函数Bar::operator T2()
。
拒绝它的编译器同意以下两者之间存在歧义:
Bar<char>
到char
的隐式用户定义转换,然后是从char
到int
的隐式内置转换,然后是显式构造函数Foo(int)。 那么,哪个编译器是对的? C ++ 14和C ++ 17之间标准的相关区别是什么?
附录 :实际错误消息
这是gcc-8.2 -std=c++17
的错误。 gcc-7.2 -std=c++14
打印相同的错误:
<source>: In function 'Foo foo(Bar<char>)':
<source>:17:17: error: call of overloaded 'Foo(Bar<char>&)' is ambiguous
return (Foo)x;
^
<source>:3:14: note: candidate: 'Foo::Foo(int)'
explicit Foo(int ptr);
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(const Foo&)'
struct Foo
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(Foo&&)'
这是来自clang-7 -std=c++14
的错误( clang-7 -std=c++17
接受代码):
<source>:17:12: error: ambiguous conversion for C-style cast from 'Bar<char>' to 'Foo'
return (Foo)x;
^~~~~~
<source>:1:8: note: candidate constructor (the implicit move constructor)
struct Foo
^
<source>:1:8: note: candidate constructor (the implicit copy constructor)
<source>:3:14: note: candidate constructor
explicit Foo(int ptr);
^
1 error generated.
这里有几种力量在起作用。 为了理解发生了什么,让我们来看看(Foo)x
应该引导我们的位置。 首先,在这种特殊情况下,c样式转换等同于static_cast
。 静态强制转换的语义是直接初始化结果对象。 由于结果对象是类类型, [dcl.init] /17.6.2告诉我们它的初始化如下:
否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数。 枚举适用的构造函数([over.match.ctor]),并通过重载决策选择最佳构造函数。 调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数。 如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。
所以重载决议选择Foo
的构造函数来调用。 如果重载解析失败,程序就会形成错误。 在这种情况下,它应该不会失败,即使我们有3个候选构造函数。 那些是Foo(int)
, Foo(Foo const&)
和Foo(Foo&&)
。
首先,我们需要将一个int
初始化为构造函数的参数,这意味着找到一个从Bar<char>
到int
的隐式转换序列。 由于您从Bar<char>
给char
的用户定义转换运算符不是显式的,我们可以从隐式对话序列Bar<char> -> char -> int
。
对于其他两个构造函数,我们需要绑定对Foo
的引用。 但是,我们不能这样做。 根据[over.match.ref] / 1 :
在[dcl.init.ref]中指定的条件下,引用可以直接绑定到glvalue或类prvalue,它是将转换函数应用于初始化表达式的结果。 重载分辨率用于选择要调用的转换函数。 假设“cv1 T”是被初始化的引用的基础类型,并且“cv S”是初始化表达式的类型,S是类类型,候选函数被选择如下:
- 考虑S及其基类的转换函数。 那些未隐藏在S中的非显式转换函数和yield类型“对cv2 T2的左值引用”(当初始化左值引用或对函数的右值引用时)或“cv2 T2”或“对cv2 T2的rvalue引用”(当初始化rvalue引用或对函数的左值引用,其中“cv1 T”与“cv2 T2”是参考兼容的([dcl.init.ref]),是候选函数。 对于直接初始化,那些未隐藏在S中的显式转换函数并且分别产生类型“对cv2 T2的左值引用”或“cv2 T2”或“对cv2 T2的rvalue引用”,其中T2与T的类型相同或者可以通过限定转换([conv.qual])转换为类型T,也是候选函数。
唯一可以产生类型为Foo
的glvalue或prvalue的转换函数是您指定的显式转换函数模板的特化。 但是,由于函数参数的初始化不是直接初始化,我们不能考虑显式转换函数。 所以我们不能在重载决策中调用副本或移动构造函数。 只留下构造函数采用int
。 因此,重载决策是成功的,应该是它。
那么为什么有些编译器会发现它不明确,或者调用模板化转换运算符呢? 好吧,由于保证复制省略被引入标准,因此注意到( CWG问题2327 )用户定义的转换函数也应该有助于复制省略。 今天,根据标准的干信,他们没有。 但我们真的很喜欢他们。 尽管有关如何完成的措辞仍在制定中,但似乎有些编制者已经开始尝试实施它。
这就是你看到的实现。 这是延伸复制省略的反对力量,干扰了过载分辨率。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.