copy-list-initial of non-copyable types

pep*_*ico 10 c++ c++11

12.6.1 - 显式初始化

struct complex {
  complex();
  complex(double);
  complex(double,double);
};

complex sqrt(complex,complex);

complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g
Run Code Online (Sandbox Code Playgroud)

8.5初始化器

14 - 表单中发生的初始化

T x = a;

以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.5.1)称为复制初始化.[注意:复制初始化可以调用移动(12.8). - 结束说明]

15 - 表单中发生的初始化

T x(a);

T x{a};

以及在新表达式(5.3.4)中,static_cast表达式(5.2.9),函数表示法类型转换(5.2.3)以及基本和成员初始化器(12.6.2)称为直接初始化.

8.5.4列表初始化[dcl.init.list]

1 - 列表初始化是从braced-init-list初始化对象或引用.这样的初始化程序称为初始化程序列表,列表的逗号分隔的初始化程序子句称为初始化程序列表的元素.初始化列表可以为空.列表初始化可以在直接初始化或复制初始化上下文中进行; 列表中的初始化 直接初始化上下文被称为在直接列表初始化和列表初始化拷贝初始化上下文被称为副本列表初始化.

原子的问题

29.6.5对原子类型的操作要求[atomics.types.operations.req]

#define ATOMIC_VAR_INIT(value) 见下文

宏扩展为适合于初始化与值一致的类型的静态存储持续时间的原子变量的常量初始化的令牌序列.[注意:此操作可能需要初始化锁. - 结束注释]即使通过原子操作,对正在初始化的变量的并发访问构成了数据竞争.[例如:

atomic<int> v = ATOMIC_VAR_INIT(5);

根据前面的部分,似乎不应该在没有复制构造函数的情况下进行赋值初始化,即使它根据§12.8.31和§12.8.32被省略,但是原子被定义为:

29.5原子类型[atomics.types.generic]

atomic() noexcept = default;
constexpr atomic(T) noexcept;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
T operator=(T) volatile noexcept;
T operator=(T) noexcept;
Run Code Online (Sandbox Code Playgroud)

没有复制构造函数!

通常,ATOMIC_VAR_INIT扩展为大括号初始化的大括号表达式,但atomic<int> v = {5}仍然是赋值初始化,并且意味着在直接构造临时构造之后复制构造.

我查看了"常量初始化"部分,看看是否存在允许没有副本的漏洞(因为"宏扩展到适合于持续初始化类型的静态存储持续时间的原子变量的令牌序列"初始化 - 兼容值")但我已经放弃了.

相关讨论:

http://thread.gmane.org/gmane.comp.lib.qt.devel/8298

http://llvm.org/bugs/show_bug.cgi?id=14486

编辑

在建立演绎过程时引用相关标准部分的答案将是理想的.

结论

因此,在Nicol Bolas的好回答之后,有趣的结论是,complex g = { 1, 2 }副本(它是复制初始化上下文)不复制(复制列表初始化解析像直接列表初始化),标准建议有一个复制操作(12.6.1 :) ...and copy/move it into g.

固定

请求:https://github.com/cplusplus/draft/pull/37

Nic*_*las 11

complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g
Run Code Online (Sandbox Code Playgroud)

这是不真实的.而且我不是说复制/移动将被省略; 我的意思是没有复制或移动.

你引用了8.5 p14,它定义T x = a;复制初始化.这是真的.但它继续定义初始化实际上是如何工作的:

从8.5,p16:

初始化器的语义如下.目标类型是要初始化的对象或引用的类型,源类型是初始化表达式的类型.如果初始化程序不是单个(可能带括号的)表达式,则不定义源类型.

  • 如果初始化程序是(非括号的)braced-init-list,则对象或引用是列表初始化的(8.5.4).

那就是这意味着复制初始化规则不适用于braced-init-list.他们使用一套单独的规则,如8.5.4所述.

你引用了8.5.4,它定义T x = {...};copy-list-initialization.你的推理出错的地方在于你从未想过复制列表初始化实际上了什么.没有复制; 这就是所谓的.

副本列表初始化的一个子集列表初始化.因此,它遵循8.5.4,p3规定的所有规则.我不打算在这里引用它们,因为它们有几页长.我将简单地解释规则如何适用complex g = {1, 2};于:

  1. 初始化列表包含元素,因此此规则不计算在内.
  2. complex 不是聚合,所以这个规则不计算在内.
  3. complex不是专业化的initializer_list,所以这条规则不算数.
  4. 根据13.3和13.3.1.7的规则,通过重载决议考虑适用的构造函数.这会找到需要两个双精度的构造函数.

因此,不会创建和复制/移动临时.

复制列表初始化直接列表初始化之间的唯一区别在13.3.1.7,p1中说明:

[...]在复制列表初始化中,如果选择了显式构造函数,则初始化是错误的.

这是和之间唯一的区别.它们都是示例,除了使用显式构造函数外,它们以统一的方式工作.complex g{1, 2}complex g = {1, 2}list-initialization


Ker*_* SB 8

构造函数-从- T明确的,并复制列表初始化是不一样的副本初始化.两者都会导致"构造函数被考虑",但复制初始化总是"考虑"复制构造函数,而列表初始化则考虑填充了列表元素的构造函数(加上一些细节).以机智:

struct Foo
{
    Foo(int) {}
    Foo(Foo const &) = delete;
};

int main()
{
    Foo f = { 1 };  // Fine
}
Run Code Online (Sandbox Code Playgroud)

(如果构造函数是explicit,那么这将失败.另外,Foo x = 1;由于删除的复制构造函数,当然会失败.)

也许是一个更具启发性的用例:

Foo make() { return { 2 }; }

void take(Foo const &);
take(make());
Run Code Online (Sandbox Code Playgroud)

所需的一切都在8.5.4/3和13.3.1.7/1中.

  • @Chico:不同的是,`的Foo F = 1;`使用户定义的转换,如`美孚F =美孚(1);`和因此将匹配拷贝构造.相比之下,列表初始化将匹配其他构造函数,如`Foo f(1);`.查看更新. (2认同)