复制列表初始化和传统复制初始化之间的任何区别? [英] Any difference between copy-list-initialization and traditional copy-initialization?

查看:165
本文介绍了复制列表初始化和传统复制初始化之间的任何区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

除了支持多个参数,不允许缩小转换,匹配构造函数采用std :: initializer_list参数,还有什么不同于复制列表初始化对传统的复制初始化?



具体来说,假设有两个用户定义的类型 A B

  class A {...}; 
class B {...};

B b;
A a1 = {b};
A a2 = b;

A code> B 会对这两种形式的初始化有所影响吗?例如是否有一个定义 A B 会使其中一个初始化合法,但另一个非法,合法但具有不同的语义,或两者都是非法的,具有不同的原因?



(假设 A 采取std :: initializer_list参数。)



编辑:添加一个链接到一个有关的我的问题:复制列表初始化的假设行为是什么?

新的拷贝列表初始化的行为被定义为是好的和一致的,但是旧的复制初始化的奇怪行为不能改变,因为向后兼容性。

正如你可以看到,在这个子句中的列表初始化的规则是相同的对于直接和复制形式。

explicit 相关的区别仅在有关重载分辨率的章节中描述。但是对于传统的初始化,直接和复制表单不相同。

传统和大括号初始化是分开定义的,所以总有潜在的一些(可能是无意的)微妙的差异。



我可以从标准的摘录中看到的差异:



1。已提及的差异




  • 不允许缩小转换次数

  • 可能有多个参数

  • 支持初始化列表构造函数,如果它们存在:

      struct A 
    {
    A(int i_):i(i_){}
    A(std :: initializer_list< int> il):i(* il.begin()+ 1){}
    int一世;
    }

    A a1 = 5; // a1.i == 5
    A a2 = {5}; // a2.i = 6





2。聚合的不同行为



对于聚合,不能使用支撑的复制构造函数,但可以使用传统的。

  struct Aggr 
{
int i;
};

Aggr aggr;
Aggr aggr1 = aggr; // OK
Aggr aggr2 = {aggr}; //形式不正确




3。存在转换运算符时参考初始化的不同行为



大括号初始化不能使用转换运算符来引用类型

  struct S 
{
operator int&(){return some_global_int;}
};

int& iref1 = s; // OK
int& iref2 = {s}; //形式不正确




4。其他类型对象初始化类型对象的一些微妙差异



这些差异在本答案结尾的标准摘录中标记为[*]




  • 旧的初始化使用用户定义的转换序列的概念(并且特别地,如上所述需要拷贝构造函数的可用性) li>
  • 大括号初始化只在适用的构造函数中执行重载解析,即大括号初始化不能使用转换为类类型的运算符



这些差异对于一些不太明显的(对我来说)像

这样的情况负责

  struct Intermediate {}; 

struct S
{
operator中间体(){return {}; }
operator int(){return 10; }
};

struct S1
{
S1(中级){}
};

S s;
中间im1 = s; // OK
中级im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK

//注意:但是大括号初始化可以使用转换操作符int
int i1 = s; // OK
int i2 = {s}; // OK




5。重载解析中的差异




  • 对显式构造函数的不同处理



请参阅 13.3.1.7通过列表初始化初始化


选择显式构造函数,
初始化是不成形的。 [注意:这不同于其他
情况(13.3.1.3,13.3.1.4),其中只有转换构造函数
被考虑用于副本初始化。此限制仅适用于
,如果此初始化是过载
分辨率的最终结果的一部分。 ]


如果您可以看到更多差异或以某种方式更正我的回答(包括语法错误) isocpp.org/files/papers/N3797.pdfrel =nofollow>当前草稿的C ++标准(我还没有找到一种方法来隐藏它们在扰动):

所有这些都位于第8.5章初始化程序中


8.5初始化程序




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


  • 如果目标类型是引用类型,请参见8.5.3。


  • 如果目标类型是字符数组,则数组 char16_t
    数组 char32_t 或数组 wchar_t ,初始化程序是一个
    字符串, 8.5.2。


  • 如果初始化程序为(),则对象为
    值初始化。 / p>


  • 否则,如果目标类型是数组,
    程序是不成形的。


  • 如果目标类型是(可能是
    cv限定)类类型:




    • 如果初始化是
      direct-initialization,或者如果它是copy-initialization,其中
      cv-unqualified版本的源类型是相同的类,或者一个
      派生类的目的地类,构造函数都是
      考虑的。适用的构造函数被枚举(13.3.1.3),并且
      最好的构造函数通过重载分辨率(13.3)来选择。调用这样选择的
      构造函数来初始化该对象,用
      初始化表达式或 expression-list 作为其参数。如果没有应用
      构造函数,或者重载解析是不明确的,则
      初始化是错误的。


    • [*] 否则(即对于
      剩余的复制初始化情况),用户定义的转换$可以从源类型转换为目标
      类型或(当使用转换函数时)到其派生类
      的b $ b序列如13.3.1.4中所述进行枚举,是通过重载分辨率(13.3)选择的
      。如果转换不能
      完成或不明确,初始化就会失败。选择的函数
      以初始化器表达式作为其参数来调用;如果
      该函数是一个构造函数,该调用初始化一个临时的
      cv-unqualified版本的目标类型。临时是一个
      的价值。然后,根据上面的
      规则,调用的结果(它是
      构造器情况的临时)用于直接初始化作为
      拷贝的目的地的对象-初始化。在某些情况下,允许一个实现
      来消除这种直接初始化中固有的复制,通过
      构造中间结果直接到初始化的
      对象中;见12.2,12.8。


    • 否则,如果源类型是
      (可能是cv限定的)类类型,转换函数是
      。适用的转换函数枚举为
      (13.3.1.5),最好的转换函数通过重载分辨率
      (13.3)选择。调用这样选择的用户定义转换,将
      的初始化器表达式转换为正在初始化的对象。如果
      转换不能完成或不明确,初始化是
      不成形。



  • 否则,
    初始化的对象的初始值是初始化的值
    表达式。如果需要
    ,则将使用标准转换(第4条)将初始化器表达式转换为目标类型的cv-unqualified
    版本;没有考虑用户定义的转换
    。如果无法完成转换,则初始化为
    格式不正确。






b $ b

8.5.3引用...






8.5.4列表初始化



类型T的对象或引用的列表初始化定义为
如下:




  • 如果 T 是聚合,则聚合初始化为
    (8.5.1)。


  • 否则,如果初始值列表没有
    元素,而 T 使用默认构造函数,对象
    是值初始化的。


  • 否则,如果 T
    的特殊化 std :: initializer_list< E> ,一个prvalue initializer_list 对象是
    ,如下所述构造,用于初始化对象
    根据一个对象的初始化规则从一个类
    相同类型(8.5)。


  • [*] 否则,如果 T
    构造函数被考虑。适用的构造函数是
    枚举,最好的构造函数是通过重载决议
    (13.3,13.3.1.7)选择的。如果需要缩小转换(见下面)到
    转换任何参数,程序是不成形的。


  • 否则,如果初始化器列表有一个 E T 不是引用类型或其引用类型是
    引用相关 E 引用从
    初始化元素;如果需要缩小转换(见下面)到
    将元素转换为 T ,程序是不成形的。


  • 否则,如果
    T 是引用类型,引用 T
    是复制列表初始化或直接列表初始化,这取决于引用的
    种初始化,将
    绑定到该临时。 [注意:像往常一样,如果引用类型是对
    非引用类型的引用,则绑定将失败,
    程序不成立。


  • 否则,如果初始值列表
    没有元素,值初始化。


  • 否则,程式无效。



$ b b


Except for supporting multiple arguments, disallowing narrowing conversion, matching constructor taking std::initializer_list argument, what else is different for copy-list-initialization against traditional copy-initialization?

To be specific, assume there are two user-defined types, A and B:

class A {...};
class B {...};

B b;
A a1 = {b};
A a2 = b;

What kind of definition of A and B will make a difference on those two forms of initialization? e.g. Is there a certain definition of A and B that will make one of the initialization legal but the other illegal, or both legal but with different semantics, or both illegal with different causes?

(Assume A doesn't have a constructor taking std::initializer_list argument.)

EDIT: Adding a link to a somewhat related question of mine: What is the supposed behavior of copy-list-initialization in the case of an initializer with a conversion operator?

解决方案

Probably, the behaviour of the new copy-list-initialization was defined to be "good" and consistent, but the "weird" behaviour of old copy-initialization couldn't be changed because of backward compatibility.
As you can see the rules for list-initialization in this clause are identical for direct and copy forms.
The difference related to explicit is described only in the chapter on overload resolution. But for traditional initialization direct and copy forms are not identical.
The traditional and brace initializations are defined separately, so there's always a potential for some (probably unintended) subtle differences.

The differences I can see from the excerpts of the standard:

1. Already mentioned differences

  • narrowing conversions are disallowed
  • multiple arguments are possible
  • braced syntax prefers initializer-list constructors if they present:

    struct A
    {
        A(int i_) : i (i_) {}
        A(std::initializer_list<int> il) : i (*il.begin() + 1) {}
        int i;
    }
    
    A a1 = 5; // a1.i == 5
    A a2 = {5}; // a2.i = 6
    


2. Different behaviour for aggregates

For aggregates you can't use braced copy-constructor, but can use traditional one.

    struct Aggr
    {
        int i;
    };

    Aggr aggr;
    Aggr aggr1 = aggr; // OK
    Aggr aggr2 = {aggr}; // ill-formed


3. Different behaviour for reference initialization in presence of conversion operator

Brace initialization can't use operators of conversion to reference type

struct S
{
    operator int&() { return some_global_int;}
};

int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed


4. Some subtle differences in initialization of object of class type by object of other type

These difference are marked by [*] in the excerpts of the Standard at the end of this answer.

  • Old initialization uses notion of user-defined conversion sequences (and, particularly, requires availability of copy constructor, as was mentioned)
  • Brace initialization just performs overload resolution among applicable constructors, i.e. brace initialization can't use operators of conversion to class type

These differences are responsible for some not very obvious (for me) cases like

struct Intermediate {};

struct S
{
    operator Intermediate() { return {}; }
    operator int() { return 10; }
};

struct S1
{
    S1(Intermediate) {}
};

S s;
Intermediate im1 = s; // OK
Intermediate im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK

// note: but brace initialization can use operator of conversion to int
int i1 = s; // OK
int i2 = {s}; // OK


5. Difference in overload resolution

  • Different treatment of explicit constructors

See 13.3.1.7 Initialization by list-initialization

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ]

If you can see more differences or somehow correct my answer (including grammar mistakes), please do.


Here are the relevant (but long) excerpts from the current draft of the C++ standard (I haven't found a way to hide them under spoiler):
All of them are located in the chapter 8.5 Initializers

8.5 Initializers

  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).

  • If the destination type is a reference type, see 8.5.3.

  • If the destination type is an array of characters, an array of char16_t, an array of char32_t, or an array of wchar_t, and the initializer is a string literal, see 8.5.2.

  • If the initializer is (), the object is value-initialized.

  • Otherwise, if the destination type is an array, the program is ill-formed.

  • If the destination type is a (possibly cv-qualified) class type:

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

    • [*] Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

    • Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

  • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions (Clause 4) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed.


8.5.3 References ...


8.5.4 List-initialization

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1).

  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

  • Otherwise, if T is a specialization of std::initializer_list<E>, a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).

  • [*] Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

  • Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. — end note ]

  • Otherwise, if the initializer list has no elements, the object is value-initialized.

  • Otherwise, the program is ill-formed.


这篇关于复制列表初始化和传统复制初始化之间的任何区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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