C ++ 17:显式转换函数与显式构造函数+隐式转换-规则是否已更改? [英] C++17: explicit conversion function vs explicit constructor + implicit conversions - have the rules changed?

查看:65
本文介绍了C ++ 17:显式转换函数与显式构造函数+隐式转换-规则是否已更改?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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也会拒绝它:

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. MSVC 2015 and 2017 accept it as well. However, gcc-8.1 and 8.2 reject it even in c++17 mode:

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

The compilers that accept it pick the templated explicit conversion function, Bar::operator T2().

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

The compilers that reject it agree that there is an ambiguity between:


  1. 显式转换函数Bar :: operator int()

  2. 首先使用来自<$的隐式用户定义转换c $ c> Bar< char> 到 char ,然后从 char int ,然后是显式构造函数Foo(int)。

  1. the explicit conversion function 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).

那么,哪个编译器正确? C ++ 14和C ++ 17之间的标准有何不同?

附录:实际错误消息

这是 gcc-8.2 -std = c ++ 17 gcc-7.2 -std = c ++ 14 打印相同的错误:

Here's the error for gcc-8.2 -std=c++17. gcc-7.2 -std=c++14 prints the same error:

<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 告诉我们它的初始化如下:

There are several forces at play here. To understand what's happening, let's examine where (Foo)x should lead us. First and foremost, that c-style cast is equivalent to a static_cast in this particular case. 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:


否则,如果初始化是直接初始化,或者这是
复制初始化,其中源
类型的cv不合格版本与
目标的类属于同一类,或者是该类的派生类。枚举适用的构造函数
([over.match.ctor]),并通过
重载解析选择最佳的构造函数。如此选择的构造函数将调用
初始化对象,并使用初始化器表达式或
expression-list作为其参数。如果没有构造函数适用,或者
重载分辨率不明确,则初始化格式错误。

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. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution. 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.

选择 Foo 的构造函数进行调用。如果重载解析失败,则程序格式错误。在这种情况下,即使我们有3个候选构造函数,它也不应该失败。它们是 Foo(int) Foo(Foo const&) Foo(Foo& &)

So overload resolution to pick the constructor of Foo to call. And if overload resolution fails, the program is ill-formed. In this case, it shouldn't fail, even though we have 3 candidate constructors. Those are Foo(int), Foo(Foo const&) and Foo(Foo&&).

首先,我们需要复制初始化 int 作为构造函数的参数,这意味着找到从 Bar< char> int 的隐式转换序列。由于您从 Bar< char> 提供给 char 的用户定义的转换运算符不明确,因此我们可以使用它来来自隐式对话序列 Bar< char> ->字符-> int

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. 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.

对于其他两个构造函数,我们需要将引用绑定到 Foo 。但是,我们不能这样做。根据 [over.match.ref] / 1

For the other two constructors, we need to bind a reference to a Foo. However, we cannot do that. According to [over.match.ref]/1 :


在[dcl.init.ref]中指定的条件下,可以将
的引用直接绑定到glvalue或类prvalue,是
将转换函数应用于初始化程序表达式的结果。重载
分辨率用于选择要调用的转换函数。
假定 cv1 T是要初始化的
的引用的基础类型,而 cv S是初始化表达式的类型,
具有S的类类型,则候选函数的选择如下:

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. 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:


  • 考虑S及其基类的转换函数。那些未隐藏在S
    中且产生类型对cv2 T2的左值引用(初始化函数的
    左值引用或右值引用时)或 cv2 T2或$的非显式转换函数b $ b对cv2 T2的右值引用(在初始化右值引用或
    对函数的左值引用时),其中 cv1 T是
    引用兼容的([dcl.init.ref])带有 cv2 T2的函数是候选
    函数。对于直接初始化,那些未隐藏在S中的显式转换
    函数会产生类型左值
    引用cv2 T2或 cv2 T2或右值引用cv2 T2
    分别是T2与T相同的类型或可以通过资格转换([conv.qual])转换为
    类型T的类型,也是
    个候选函数。

唯一可以为我们生成 Foo 是您指定的显式转换函数模板的专业化版本。但是,由于函数参数的初始化不是直接初始化,因此无法考虑显式转换函数。因此,我们不能以重载分辨率调用副本或移动构造函数。剩下的只有构造函数使用 int 了。因此,重载解析是成功的,应该是成功的。

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. 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. So overload resolution is a success, and that should be it.

那么,为什么有些编译器发现它是模棱两可的,或者调用模板转换运算符呢?好吧,由于保证复制省略已引入标准中,因此已被注意到( CWG问题2327 ),用户定义的转换功能也应有助于简化复制。今天,根据标准的干字母,他们没有。但我们真的很希望他们这样做。虽然确切的措词仍在制定中,但似乎有些编译器已经开始尝试实现它。

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. 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.

这篇关于C ++ 17:显式转换函数与显式构造函数+隐式转换-规则是否已更改?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆