Member initialization for non-copyable variable in C++17

Behavior changed since C++17, which requires compilers to omit the copy/move construction in std::atomic<int> a = 0;, i.e. guaranteed copy elision.

(emphasis mine)

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

In details, std::atomic<int> a = 0; performs copy initialization:

If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary (until C++17) prvalue expression (since C++17) if a converting constructor was used, is then used to direct-initialize the object.

and

(emphasis mine)

if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object

That means a is initialized from 0 directly, there's no temporary to be constructed and then no longer a temporary to copy/move from.

Before C++17, in concept std::atomic<int> a = 0; requires a temporary std::atomic to be constructed from 0, then the temporary is used to copy-construct a.

Even copy elision is allowed before C++17, it's considered as an optimization:

(emphasis mine)

This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

That's why gcc triggers diagnostic in pre-c++17 mode for std::atomic<int> a = 0; .

(emphasis mine)

Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.

BTW: I suppose there was a bug in g++ 6.5.0 with -std=c++17; and it has been fixed in later version.


Which one is correct here?

The 7.4.0 is correct. The copy can be elided for this case which is why it is Ok. (although this requires c++17).

(see https://en.cppreference.com/w/cpp/language/copy_initialization for more details)