Why does the initialisation of an object invoke the copy constructor?
Asked Answered
R

1

8

Consider the following minimal working example:

#include <atomic>

int main() {
  ::std::atomic<bool> a = false;
}

Copy ctor and copy assignment of atomic are both explicitly deleted. However, this should invoke the ctor taking exactly a bool.

Both g++ and clang++ complain that this line is attempting to invoke the copy ctor of atomic:

$ g++ -std=c++1z a.cpp 
a.cpp: In function ‘int main()’:
a.cpp:4:27: error: use of deleted function ‘std::atomic<bool>::atomic(const std::atomic<bool>&)’
   ::std::atomic<bool> a = false;
                           ^~~~~
$ clang++ -std=c++1z a.cpp 
a.cpp:4:23: error: copying variable of type '::std::atomic<bool>' invokes deleted constructor
  ::std::atomic<bool> a = false;
                      ^   ~~~~~

Why are they trying to copy an atomic?

Rosarosabel answered 20/2, 2018 at 10:33 Comment(6)
Maybe your clang version is too old. Cannot reproduce: wandbox.org/permlink/InaivNs3hFjSUuXsSyncrisis
In g++ it seem to got fixed at 7.x versions.Burnt
Oh yes, when I compile with clang 5.0 it works. It seems like a compiler bug in both gcc and clang.Rosarosabel
It seems that this is compiled to ::std::atomic<bool> a = ::std::atomic<bool>(false);, It creates a new std::atomic<bool>(false), then copies it. But, when something like this is done ::std::atomic<bool> a ( false );, it works. Can a compiler guarantee copy elision, I think C++17 guarantees it ??Phyto
Yes that was what I thought as well and the braces alternative was my workaround. But I was wondering why the compiler would compile this to this statement. But it seems to be a bug.Rosarosabel
@Phyto - This is ultimately copy initialization, in any standard revision. C++14 also had wording that allowed initializing directly. It just wasn't really a hard requirement prior to C++17.Grajeda
I
1

It tries to invoke the copy constructor because its move constructor has been implicitly deleted.

Suppose we have a class X.

struct X
{
    X(const X&) = delete; // user-declared

    X(int)
    {
    }
};

Now, if you were to write

X x = 4;

it would be the same as

X x = X(4); // copy/move into x, see 15.6.1

and you would get a compilation error because you explicitly deleted the copy constructor and therefore no move constructor is being implicitly declared.

15.8.1 Copy/move constructors

[...]

If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator, and
  • X does not have a user-declared destructor.

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]

In C++17 this changes with the introduction of guaranteed copy elision.
This results in the line being equal to

X x(4);

which does not rely on either copy or move constructor and instead calls X(int).

Like X, std::atomic also has its copy constructor explicitly deleted which is why your code fails to compile if not compiled with C++17 support.

Introrse answered 20/2, 2018 at 12:31 Comment(7)
"it would be the same as" Where does it say that? It is my understanding that the syntax Type var = value; and Type var(value); did the same in C++, even before 17.Rosarosabel
It says so in the standard as I wrote.Introrse
15.8.1 seems to be talking about whether or not these are defined or not. It doesn't seem to address what the initialisation syntax means (the note doesn't address this, as far as I can tell).Rosarosabel
@Rosarosabel In C++14, it says so in [dcl.init]p15-p17.Whippoorwill
@Rosarosabel Type var = value; is a form of "copy-initialization" and Type var(value); is a form of "direct-initialization", and these have always been different. The main difference is that copy-initialization will not use a constructor marked explicit, but before C++17, a move or copy constructor was also involved in copy-initialization.Wilhite
Huh, I just checked the standard. This was new to me. Thanks.Rosarosabel
It's amazing how a simple one-liner initialization invokes two complex concepts - copy and move - at the same timeVivisect

© 2022 - 2024 — McMap. All rights reserved.