在 C++14 允许的情况下,C++17 是否禁止复制省略?

Does C++17 forbid copy elision in a case where C++14 allowed it?

考虑以下几点:

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

在 C++14 中,由于 [class.copy]/31,

,尽管移动构造函数有副作用,但移动可以被省略

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:

,此举肯定会被省略

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 ]

至此,我所陈述的事实是众所周知的。但现在让我们更改代码,使其显示为:

X x({});

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

在 C++17 中,重载决议是相同的,但现在 [dcl.init]/17.6.1 不适用,C++14 中的项目符号不再存在。没有初始化表达式,因为初始化器是花括号初始化列表。相反,[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. 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.

这似乎需要调用移动构造函数,如果标准中的其他地方有一条规则说可以省略它,我不知道它在哪里。

如T.C。指出,这与 CWG 2327:

类似

Consider an example like:

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

Dog d;
Cat c(d);

This goes to 11.6 [dcl.init] bullet 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. 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的移动构造函数。根据 11.6.3 [dcl.init.ref] 项目符号 5.2.1.2,初始化构造函数的 Cat&& 参数会产生一个临时值。这排除了这种情况下复制省略的可能性。

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

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

正如我在评论中提到的,我不确定这在 C++14 中是否有所不同。为了计算 X x({}),我们必须构造一个 X,我们绑定到移动构造函数的右值引用参数 - 我们不能在那个时候省略移动,引用绑定发生在我们知道我们正在采取行动之前。