copy-list-initialization of non-copyable types

后端 未结 2 641
清歌不尽
清歌不尽 2021-02-20 10:45

12.6.1 - Explicit initialization

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

complex sqrt(complex,complex);

         


        
相关标签:
2条回答
  • 2021-02-20 11:28
    complex g = { 1, 2 };  // construct complex(1, 2) 
                           // using complex(double, double) 
                           // and *copy/move* it into g
    

    This is untrue. And I'm not saying that the copy/move will be elided; I mean that there will be no copying or moving.

    You quoted 8.5 p14, which defines T x = a; as copy-initialization. This is true. But it then goes on to define how initialization actually works:

    From 8.5, p16:

    The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

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

    That right there means that copy-initialization rules do not apply to a braced-init-list. They use a separate set of rules, as covered in 8.5.4.

    You quoted 8.5.4, which defines T x = {...}; as copy-list-initialization. Where your reasoning goes wrong is that you never looked up what copy-list-initialization actually does. There is no copying; that's just what it's called.

    copy-list-initialization is a subset of list-initialization. Therefore, it follows all of the rules laid down by 8.5.4, p3. I'm not going to quote them here, because they're several pages long. I'll simply explain how the rules apply to complex g = {1, 2};, in order:

    1. The initializer list has elements, so this rule doesn't count.
    2. complex is not an aggregate, so this rule doesn't count.
    3. complex is not a specialization of initializer_list, so this rule doesn't count.
    4. Applicable constructors are considered via overload resolution, in accord with the rules of 13.3 and 13.3.1.7. This finds the constructor that takes two doubles.

    Therefore, no temporary will be created and copied/moved in.

    The only difference between copy-list-initialization and direct-list-initialization is stated in 13.3.1.7, p1:

    [...] In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

    That is the only difference between complex g{1, 2} and complex g = {1, 2}. They are both examples of list-initialization, and they work in a uniform way except for the use of explicit constructors.

    0 讨论(0)
  • 2021-02-20 11:28

    The constructor-from-T is not explicit, and copy-list-initialization is not the same as copy-initialization. Both cause "constructors to be considered", but copy-initialization always "considers" the copy con­struc­tor, while list-initialization considers constructors with the list elements filled in (plus some details). To wit:

    struct Foo
    {
        Foo(int) {}
        Foo(Foo const &) = delete;
    };
    
    int main()
    {
        Foo f = { 1 };  // Fine
    }
    

    (This would fail if the constructor was explicit. Also, Foo x = 1; will of course fail on account of the deleted copy constructor.)

    Perhaps an even more enlightening use case:

    Foo make() { return { 2 }; }
    
    void take(Foo const &);
    take(make());
    

    Everything necessary for this is in 8.5.4/3 and in 13.3.1.7/1.

    0 讨论(0)
提交回复
热议问题