简体   繁体   English

C ++ 17:显式转换函数与显式构造函数+隐式转换 - 规则是否已更改?

[英]C++17: explicit conversion function vs explicit constructor + implicit conversions - have the rules changed?

Clang 6, clang 7, and gcc 7.1, 7.2, and 7.3 all agree that the following is valid C++17 code, but is ambiguous under C++14 and C++11. Clang 6,clang 7和gcc 7.1,7.2和7.3都同意以下是有效的C ++ 17代码,但在C ++ 14和C ++ 11下是不明确的。 MSVC 2015 and 2017 accept it as well. MSVC 2015和2017也接受它。 However, gcc-8.1 and 8.2 reject it even in c++17 mode: 但是,即使在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;
}

The compilers that accept it pick the templated explicit conversion function, Bar::operator T2() . 接受它的编译器选择模板显式转换函数Bar::operator T2()

The compilers that reject it agree that there is an ambiguity between: 拒绝它的编译器同意以下两者之间存在歧义:

  1. the explicit conversion function Bar::operator int() 显式转换函数Bar :: operator int()
  2. first using the implicit user-defined conversion from Bar<char> to char , then the implicit built-in conversion from char to int , and then the explicit constructor Foo(int). 首先使用从Bar<char>char的隐式用户定义转换,然后是从charint的隐式内置转换,然后是显式构造函数Foo(int)。

So, which compiler is right? 那么,哪个编译器是对的? What is the relevant difference in the standard between C++14 and C++17? C ++ 14和C ++ 17之间标准的相关区别是什么?


Appendix : actual error messages 附录 :实际错误消息

Here's the error for gcc-8.2 -std=c++17 . 这是gcc-8.2 -std=c++17的错误。 gcc-7.2 -std=c++14 prints the same error: 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&&)'

And here's the error from clang-7 -std=c++14 ( clang-7 -std=c++17 accepts the code): 这是来自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.

There are several forces at play here. 这里有几种力量在起作用。 To understand what's happening, let's examine where (Foo)x should lead us. 为了理解发生了什么,让我们来看看(Foo)x应该引导我们的位置。 First and foremost, that c-style cast is equivalent to a static_cast in this particular case. 首先,在这种特殊情况下,c样式转换等同于static_cast And the semantics of the static cast would be to direct-initialize the result object. 静态强制转换的语义是直接初始化结果对象。 Since the result object would be of a class type, [dcl.init]/17.6.2 tells us it's initialized as follows: 由于结果对象是类类型, [dcl.init] /17.6.2告诉我们它的初始化如下:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. 否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数。 The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution. 枚举适用的构造函数([over.match.ctor]),并通过重载决策选择最佳构造函数。 The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). 调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数。 If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed. 如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。

So overload resolution to pick the constructor of Foo to call. 所以重载决议选择Foo的构造函数来调用。 And if overload resolution fails, the program is ill-formed. 如果重载解析失败,程序就会形成错误。 In this case, it shouldn't fail, even though we have 3 candidate constructors. 在这种情况下,它应该不会失败,即使我们有3个候选构造函数。 Those are Foo(int) , Foo(Foo const&) and Foo(Foo&&) . 那些是Foo(int)Foo(Foo const&)Foo(Foo&&)

For the first ,we need to copy initialize an int as an argument to the constructor, and that means find an implicit conversion sequence from Bar<char> to int . 首先,我们需要将一个int初始化为构造函数的参数,这意味着找到一个从Bar<char>int的隐式转换序列。 Since the user defined conversion operator you provided from Bar<char> to char is not explicit, we can use it to from an implicit conversation sequence Bar<char> -> char -> int . 由于您从Bar<char>char的用户定义转换运算符不是显式的,我们可以从隐式对话序列Bar<char> -> char -> int

For the other two constructors, we need to bind a reference to a Foo . 对于其他两个构造函数,我们需要绑定对Foo的引用。 However, we cannot do that. 但是,我们不能这样做。 According to [over.match.ref]/1 : 根据[over.match.ref] / 1

Under the conditions specified in [dcl.init.ref], a reference can be bound directly to a glvalue or class prvalue that is the result of applying a conversion function to an initializer expression. 在[dcl.init.ref]中指定的条件下,引用可以直接绑定到glvalue或类prvalue,它是将转换函数应用于初始化表达式的结果。 Overload resolution is used to select the conversion function to be invoked. 重载分辨率用于选择要调用的转换函数。 Assuming that “cv1 T” is the underlying type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows: 假设“cv1 T”是被初始化的引用的基础类型,并且“cv S”是初始化表达式的类型,S是类类型,候选函数被选择如下:

  • The conversion functions of S and its base classes are considered. 考虑S及其基类的转换函数。 Those non-explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) or “ cv2 T2” or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function), where “cv1 T” is reference-compatible ([dcl.init.ref]) with “cv2 T2”, are candidate functions. 那些未隐藏在S中的非显式转换函数和yield类型“对cv2 T2的左值引用”(当初始化左值引用或对函数的右值引用时)或“cv2 T2”或“对cv2 T2的rvalue引用”(当初始化rvalue引用或对函数的左值引用,其中“cv1 T”与“cv2 T2”是参考兼容的([dcl.init.ref]),是候选函数。 For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” or “cv2 T2” or “rvalue reference to cv2 T2,” respectively, where T2 is the same type as T or can be converted to type T with a qualification conversion ([conv.qual]), are also candidate functions. 对于直接初始化,那些未隐藏在S中的显式转换函数并且分别产生类型“对cv2 T2的左值引用”或“cv2 T2”或“对cv2 T2的rvalue引用”,其中T2与T的类型相同或者可以通过限定转换([conv.qual])转换为类型T,也是候选函数。

The only conversion function that can yield us a glvalue or prvalue of type Foo is a specialization of the explicit conversion function template you specified. 唯一可以产生类型为Foo的glvalue或prvalue的转换函数是您指定的显式转换函数模板的特化。 But, because initialization of function arguments is not direct initialization, we cannot consider the explicit conversion function. 但是,由于函数参数的初始化不是直接初始化,我们不能考虑显式转换函数。 So we cannot call the copy or move constructors in overload resolution. 所以我们不能在重载决策中调用副本或移动构造函数。 That leaves us only with the constructor taking an int . 只留下构造函数采用int So overload resolution is a success, and that should be it. 因此,重载决策是成功的,应该是它。

Then why do some compilers find it ambiguous, or call the templated conversion operator instead? 那么为什么有些编译器会发现它不明确,或者调用模板化转换运算符呢? Well, since guaranteed copy elision was introduced into the standard, it was noted ( CWG issue 2327 ) that user defined conversion functions should also contribute to copy elision. 好吧,由于保证复制省略被引入标准,因此注意到( CWG问题2327 )用户定义的转换函数也应该有助于复制省略。 Today, according to the dry letter of the standard, they do not. 今天,根据标准的干信,他们没有。 But we'd really like them to. 但我们真的很喜欢他们。 While the wording for exactly how it should be done is still being worked out, it would seem that some compilers already go ahead and try to implement it. 尽管有关如何完成的措辞仍在制定中,但似乎有些编制者已经开始尝试实施它。

And it's that implementation that you see. 这就是你看到的实现。 It's the opposing force of extending copy elision that interferes with overload resolution here. 这是延伸复制省略的反对力量,干扰了过载分辨率。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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