在C ++ 14允许的情况下,C ++ 17禁止复制省略吗? [英] Does C++17 forbid copy elision in a case where C++14 allowed it?

查看:99
本文介绍了在C ++ 14允许的情况下,C ++ 17禁止复制省略吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下内容:

struct X {
    X() {}
    X(X&&) { puts("move"); }
};
X x = X();

在C ++ 14中,尽管move构造函数由于[class.copy]/31而产生副作用,但仍可以忽略这一步,

In C++14, the move could be elided despite the fact that the move constructor has side effects thanks to [class.copy]/31,

在以下情况下允许复制/移动操作的省略...当未绑定到引用(12.2)的临时类对象将被复制/移动时 到具有相同cv-unqualified类型的类对象

This elision of copy/move operations ... is permitted in the following circumstances ... when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type

在C ++ 17中,此项目符号已删除.相反,由于[dcl.init]/17.6.1,可以保证消除该移动:

In C++17 this bullet was removed. Instead the move is guaranteed to be elided thanks to [dcl.init]/17.6.1:

如果初始值设定项表达式是prvalue,并且源类型的cv不合格版本相同 class作为目标的类,初始化器表达式用于初始化目标 目的. [示例: T x = T(T(T()));调用T默认构造函数来初始化x. — 结束 例子]

If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example ]

到目前为止,我所说的事实是众所周知的.但是现在让我们更改代码,使其显示为:

Thus far the facts I've stated are well-known. But now let's change the code so that it reads:

X x({});

在C ++ 14中,将执行重载解析,并使用默认构造函数将{}转换为类型为X的临时类型,然后将其移至x中.复制省略规则可以消除此举动.

In C++14, overload resolution is performed and {} is converted to a temporary of type X using the default constructor, then moved into x. The copy elision rules allow this move to be elided.

在C ++ 17中,重载分辨率是相同的,但是现在[dcl.init]/17.6.1不适用,并且C ++ 14的项目符号也不再存在.没有初始化器表达式,因为初始化器是一个braced-init-list.相反,似乎适用[dcl.init]/(17.6.2):

In C++17, the overload resolution is the same, but now [dcl.init]/17.6.1 doesn't apply and the bullet from C++14 isn't there anymore. There is no initializer expression, since the initializer is a braced-init-list. Instead it appears that [dcl.init]/(17.6.2) applies:

否则,如果初始化是直接初始化,或者如果是复制初始化,则 源类型的cv非限定版本与该类的类相同,或者是该类的派生类 目的地,考虑构造函数.列举了适用的构造函数(16.3.1.3), 最佳解决方案是通过过载分辨率(16.3)选择的.这样选择的构造函数称为 使用初始化器表达式或表达式列表作为其参数来初始化对象.如果不 构造函数适用,或者重载解决方案不明确,初始化格式错误.

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 (16.3.1.3), and the best one is chosen through overload resolution (16.3). 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.

这似乎需要调用move构造函数,并且如果标准中的其他规则规定可以取消它,我不知道它在哪里.

This appears to require the move constructor to be called, and if there's a rule elsewhere in the standard that says it's ok to elide it, I don't know where it is.

推荐答案

以T.C.指出,这与 CWG 2327类似:

As T.C. points out, this is in a similar vein to CWG 2327:

考虑如下示例:

Consider an example like:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

这转到11.6 [dcl.init]项目符号17.6.2:

This goes to 11.6 [dcl.init] bullet 17.6.2:

否则,如果初始化是直接初始化,或者如果初始化是复制初始化,其中源类型的cv不合格版本与目标的类相同,或为该类的派生类,则构造函数为经过考虑的.列举了适用的构造函数(16.3.1.3 [over.match.ctor]),并通过重载分辨率(16.3 [over.match])选择了最佳的构造函数.如此选择的构造函数将被调用以初始化对象,并使用初始化表达式或 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 (16.3.1.3 [over.match.ctor]), and the best one is chosen through overload resolution (16.3 [over.match]). 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.

重载分辨率选择Cat的move构造函数.根据11.6.3 [dcl.init.ref]项目符号5.2.1.2,初始化构造函数的Cat&&参数将导致临时出现.这排除了在这种情况下复制删除的可能性.

Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 11.6.3 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.

这似乎是措辞更改中疏于保证复制副本的疏忽.在这种情况下,我们大概应该同时考虑构造函数和转换函数,就像复制初始化一样,但是我们需要确保不会引入任何新颖的问题或歧义.

This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.

使这成为潜在问题的原因是我们有一个错误的类型的初始化程序(在OP中为{},在本示例中为d)-我们需要将其转换为正确的类型(XCat),但要弄清楚该怎么做,我们需要执行重载解析.这个已经已将我们带到move构造函数-在这里,我们将该右值引用参数绑定到一个刚创建的新对象上,以实现此目的.在这一点上,取消这一举动为时已晚.我们已经在那了.我们不能...备份,ctrl-z,中止中止,好了,重新开始.

What makes this the same underlying problem is that we have an initializer (in OP, {}, in this example, d) that's the wrong type - we need to convert it to the right type (X or Cat), but to figure out how to do that, we need to perform overload resolution. This already gets us to the move constructor - where we're binding that rvalue reference parameter to a new object that we just created to make this happen. At this point, it's too late to elide the move. We're already there. We can't... back up, ctrl-z, abort abort, okay start over.

正如我在评论中提到的那样,我不确定在C ++ 14中这是否也有所不同.为了评估X x({}),我们必须构造一个要绑定到move构造函数的右值引用参数的X-我们无法在此时忽略该移动,引用绑定发生在我们甚至不知道之前我们正在采取行动.

As I mentioned in the comments, I'm not sure this was different in C++14 either. In order to evaluate X x({}), we have to construct an X that we're binding to the rvalue reference parameter of the move constructor - we can't elide the move at that point, the reference binding happens before we even know we're doing a move.

这篇关于在C ++ 14允许的情况下,C ++ 17禁止复制省略吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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