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

问题描述 投票:12回答:1

考虑以下:

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

在C ++ 14中,尽管移动构造函数由于[class.copy] / 31而具有副作用,但移动可能会被省略,

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

在C ++ 17中,这个子弹被删除了。相反,由于[dcl.init] /17.6.1,保证可以省略此举:

如果初始化表达式是prvalue且源类型的cv-nonqualified版本与目标类相同,则初始化表达式用于初始化目标对象。 [示例:T x = T(T(T()));调用T默认构造函数来初始化x。 - 结束例子]

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

X x({});

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

在C ++ 17中,重载决策是相同的,但现在[dcl.init] /17.6.1不适用,并且C ++ 14中的子弹不再存在。没有初始化表达式,因为初始化程序是braced-init-list。相反,似乎[dcl.init] /(17.6.2)适用:

否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数。列举了适用的构造函数(16.3.1.3),并通过重载解析(16.3)选择最佳构造函数。调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。

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

c++ language-lawyer copy-elision c++17
1个回答
5
投票

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

考虑一个例子:

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

Dog d;
Cat c(d);

这是11.6 [dcl.init] bullet 17.6.2:

否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数。枚举适用的构造函数(16.3.1.3 [over.match.ctor]),并通过重载决策(16.3 [over.match])选择最佳构造函数。调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数。如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的。

重载分辨率选择Cat的移动构造函数。初始化构造函数的Cat&&参数会产生一个临时的,每11.6.3 [dcl.init.ref]子弹5.2.1.2。这排除了这种情况下复制省略的可能性。

这似乎是对保证副本省略的措辞变更的疏忽。在这种情况下,我们应该同时考虑构造函数和转换函数,就像复制初始化一样,但我们需要确保不会引入任何新颖的问题或含糊之处。

使这个问题成为同一根本问题的原因是我们有一个初始化器(在OP,{},在这个例子中,d)是错误的类型 - 我们需要将它转换为正确的类型(XCat),但要弄清楚如何为此,我们需要执行重载决策。这已经让我们进入了移动构造函数 - 我们将rvalue引用参数绑定到我们刚刚创建的新对象以实现此目的。在这一点上,为了避免这一举动为时已晚。我们已经在那里了。我们不能......备份,ctrl-z,中止中止,好吧重新开始。

正如我在评论中提到的,我不确定这在C ++ 14中是否有所不同。为了评估X x({}),我们必须构造一个X,我们绑定到移动构造函数的rvalue引用参数 - 我们不能忽略那一点上的移动,引用绑定发生在我们甚至知道我们正在做的事情之前一个动作。

© www.soinside.com 2019 - 2024. All rights reserved.