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.