C++11 – Copy-List Initialization of Non-Copyable Types

c++c++11

12.6.1 – Explicit initialization

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

8.5 Initializers

14 – The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception
(15.1), handling an exception (15.3), and aggregate member
initialization (8.5.1) is called copy-initialization. [Note:
Copy-initialization may invoke a move (12.8). — end note ]

15 – The initialization that occurs in the forms

T x(a);

T x{a};

as well as in new expressions (5.3.4), static_cast expressions
(5.2.9), functional notation type conversions (5.2.3), and base and
member initializers (12.6.2) is called direct-initialization.

8.5.4 List-initialization [dcl.init.list]

1 – List-initialization is initialization of an object or reference from
a braced-init-list. Such an initializer is called an initializer list,
and the comma-separated initializer-clauses of the list are called the
elements of the initializer list. An initializer list may be empty.
List-initialization can occur in direct-initialization or copy-initialization
contexts; list-initialization in a
direct-initialization context is called direct-list-initialization and
list-initialization in a copy-initialization context is called
copy-list-initialization.

The problem with atomics

29.6.5 Requirements for operations on atomic types [atomics.types.operations.req]

#define ATOMIC_VAR_INIT(value) see below

The macro expands to a token sequence suitable for constant
initialization of an atomic variable of static storage duration of a
type that is initialization-compatible with value. [Note: This
operation may need to initialize locks. — end note ] Concurrent access
to the variable being initialized, even via an atomic operation,
constitutes a data race. [ Example:

atomic<int> v = ATOMIC_VAR_INIT(5);

According to previous sections it seems there shouldn't be assignment initialization without a copy-constructor involved, even if it's elided according to §12.8.31 and §12.8.32, but atomics are defined as:

29.5 Atomic types [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;

There's no copy-constructor!

Frequently, ATOMIC_VAR_INIT expands to a brace expression for brace initialization, but atomic<int> v = {5} is still an assignment initialization and would imply copy construction after direct construction of a temporary.

I've looked over the "constant initialization" section to see whether there's a loophole allowing this without a copy (because of "The macro expands to a token sequence suitable for constant initialization of an atomic variable of static storage duration of a type that is initialization-compatible with value") but I'm already giving up.

Related discussions:

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

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

EDIT

An answer quoting the relevant standard sections while building a deduction process would be ideal.

CONCLUSION

So, after the nice answer by Nicol Bolas, the funny conclusion is that complex g = { 1, 2 } is a copy (it is copy-initialization context) which don't copy (copy-list-initialization resolves like direct-list-initialization) for which the standard suggests there's a copy operation (12.6.1: ...and copy/move it into g).

FIX

Pull request: https://github.com/cplusplus/draft/pull/37

Best Answer

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.

Related Question