Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

When performing member initialization for non-copyable variable (such as std::atomic<int>), it's required to use direct-initialization rather than copy-initialization according to answer here. However when I turn on -std=c++17 in g++ 7.4.0, it seems that the latter one also works well.

#include <atomic>

class A {
    std::atomic<int> a = 0;     // copy-initialization
    std::atomic<int> b{0};      // direct-initialization
};
$ g++ -c atomic.cc -std=c++11    // or c++14
atomic.cc:4:26: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’
     std::atomic<int> a = 0;     // copy-initialization

$ g++ -c atomic.cc -std=c++17
// no error

It also failed when compiling with g++ 6.5.0 even with -std=c++17. Which one is correct here?

like image 823
zingdle Avatar asked Sep 30 '19 08:09

zingdle


People also ask

How do you initialize a member variable?

To initialize a class member variable, put the initialization code in a static initialization block, as the following section shows. To initialize an instance member variable, put the initialization code in a constructor.

What is meant by copy initialization?

The Copy initialization can be done using the concept of copy constructor. As we know that the constructors are used to initialize the objects. We can create our copy constructor to make a copy of some other object, or in other words, initialize current object with the value of another object.


Video Answer


2 Answers

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.

like image 107
songyuanyao Avatar answered Nov 15 '22 22:11

songyuanyao


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)

like image 43
darune Avatar answered Nov 15 '22 22:11

darune