来自花括号初始化列表的直接初始化中的复制省略

3
在下面的程序中,对象 A a 直接从花括号初始化列表 {A{}} 初始化:
#include <iostream>

struct A {
    int v = 0;
    A() {}
    A(const A &) : v(1) {}
};

int main() {
    A a({A{}});
    std::cout << a.v;
}

MSVC和GCC在此处打印0,意味着发生了复制省略。而Clang执行复制构造函数并打印1。 在线演示:https://gcc.godbolt.org/z/1vqvf148z

哪个编译器是正确的?


2
你怎么知道其中任何一个是“正确的”? - Nicol Bolas
1
基本上,您可以访问 https://timsong-cpp.github.io/cppwp/n4861/dcl.init#17.6.2。可能还应该考虑另一种情况,即 https://cplusplus.github.io/CWG/issues/2311.html。 - Language Lawyer
1
据我所知,两者都是如此。你的复制构造函数具有副作用(可能会被省略)。如果需要副作用,则由程序员明确避免复制省略。如果我错了,请指正 :) - Pepijn Kramer
@PepijnKramer,如果删除复制构造函数,则Clang/MSVC将拒绝该程序,而GCC仍然接受它,在线演示:https://gcc.godbolt.org/z/EWq4qW79a 所有编译器都正确吗? :) - Fedor
1个回答

1
哪个编译器在这里是正确的?
我认为使用复制构造函数并打印1的clang是正确的,原因如下所述。
首先请注意,A a({A {}});是直接初始化,如dcl.init#16.1所示:
16)发生的初始化: 16.1)对于表达式列表或大括号初始化列表的初始化, 16.2)用于新的初始化程序, 16.3)在static_cast表达式中([expr.static.cast]),
现在,dcl.init#17.6适用于此处:
如果目标类型是一个(可能带有cv限定符的)类类型:

17.6.1 如果初始化表达式是prvalue,并且源类型的cv-unqualified版本与目标类型的类相同,则使用初始化表达式来初始化目标对象。(例如:T x = T(T(T())); 调用T的默认构造函数来初始化x。)

17.6.2 否则,如果初始化是直接初始化,或者是复制初始化且源类型的cv-unqualified版本与目标类型的类相同或派生自其类,则考虑构造函数。

适用的构造函数将被枚举([over.match.ctor]),并通过重载决议([over.match])选择最佳构造函数。然后:

17.6.2.1 如果重载决议成功,则调用所选的构造函数,以初始化对象,并将初始化表达式或表达式列表作为其参数。

这意味着复制构造函数(在此处为所选的构造函数)将被用于初始化名为a的对象,并将表达式列表作为其参数,由于您的复制构造函数的成员初始化列表中将a.v初始化为1,因此clang输出打印1是正确的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接