繁体   English   中英

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()

拒绝它的编译器同意以下两者之间存在歧义:

  1. 显式转换函数Bar :: operator int()
  2. 首先使用从Bar<char>char的隐式用户定义转换,然后是从charint的隐式内置转换,然后是显式构造函数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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM