Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does defining an empty copy ctor beside a deleted default ctor make a value initialization with empty list fail?

In short, why the code below behaves like described in the comments?

struct A
{
    A() = delete;
    //A(const A&) {} // uncommenting this...
};

int main()
{
    A a{}; // ... breaks this
    //A(); // this fails in either case because of `A() = delete;` only
}

What part of the standard (or at least a page on cppreference) should I look at to understand this?

However, writing A(const A&) = default; instead of //A(const A&) {} doesn't break A a{};. What about this? I think the underlying cause is the same, but a word from who really knows C++ is better than what I think.

like image 445
Enlico Avatar asked Jan 13 '21 07:01

Enlico


People also ask

What will happen when copy constructor makes equal to delete?

The copy constructor and copy-assignment operator are public but deleted. It is a compile-time error to define or call a deleted function. The intent is clear to anyone who understands =default and =delete . You don't have to understand the rules for automatic generation of special member functions.

What does the default copy constructor do?

Default Copy Constructor This is called a default or standard copy constructor. What it does is simply copy data members of the object passed to it to the corresponding data members of the new object.

How are an object's data members initialized if a class has only an implicitly defined default constructor?

Implicitly defined default contructor means all data members have default constructors. Therefore, default constructor for each data member is called in order they are declared in and before the composite class constructor body.

What does default mean in C++?

A default argument is a value provided in a function declaration that is automatically assigned by the compiler if the calling function doesn't provide a value for the argument. In case any value is passed, the default value is overridden.


2 Answers

Without the user-provided copy constructor, A is an aggregate. Yes, even though we deleted the default constructor. It's something that was addressed in C++20.

So, prior to C++20, A a{}; is aggregate initialization, and so doesn't use the deleted constructor. When you uncomment the copy constructor, A stops being an aggregate, thus turning the aggregate initialization into value initialization. So the initialization of a will attempt to call the deleted constructor.

To divine the meaning of an initializer from the standard, one typically starts at [dcl.init]/16. Going through the bullets, one can find how the properties of the initializer (when matched with the properties of the types in question) will affect the way initialization occurs.

like image 107
StoryTeller - Unslander Monica Avatar answered Nov 11 '22 01:11

StoryTeller - Unslander Monica


Aggregate initialization is not value initialization

If a given class, say S, is an aggregate, then

S s{};

is aggregate initialization, not value initialization, and will bypass any constructors of S, even if they are deleted or private. The result of aggregate initialization is typically value-initialization of the (public(1)) data members of the aggregate.

(1) As shown below, a class with any private data member is never an aggregate class, not through any of the C++11 through C++20 standards.


The definition of what is an aggregate class is governed by:

  • [dcl.init.aggr]/1 in C++11 (N3337)
  • [dcl.init.aggr]/1 in C++14 (N4140)
  • [dcl.init.aggr]/1 in C++17 (N4659)
  • [dcl.init.aggr]/1 in C++20 (N4861)

where the typical confusion with aggregates is the weaker requirement of C++11 through C++17 to not have any user-provided(2) constructors, which was made much stricter in C++20 with the requirement to not have any user-declared constructors.

(2) The definition of what counts as user-provided function is governed by [dcl.fct.def.default]/4 in C++11 and [dcl.fct.def.default]/5 in C++14 through C++20; the definition of user-provided is essentially the same in all these language versions.

There are also other changes to the definition of aggregates between C++11, C++14 and C++17 that are not as well-known, but that likewise add to the aggregate confusion prior to C++20; we'll walk through a couple of examples to highlight them.


Is the example an aggregate?

For each example below; if the class S of the example is an aggregate (for a particular language version), the following is well-formed:

S s{};

The aggregate rules in all the examples above likewise applies for any kind of constructor (given that they are otherwise applicable, e.g. allows to be explicitly defaulted or deleted), not just S().


struct S {};
  • C++11: yes
  • C++14: yes
  • C++17: yes
  • C++20: yes

struct S {
    int x;
};
  • C++11: yes
  • C++14: yes
  • C++17: yes
  • C++20: yes

struct S {
    int x{42};
};
  • C++11: no (has a default member initializer)
  • C++14: yes
  • C++17: yes
  • C++20: yes

class S {
    int x;
};
  • C++11: no
  • C++14: no
  • C++17: no
  • C++20: no

Has a private data member.


struct S {
    S() = default;
};
  • C++11: yes
  • C++14: yes
  • C++17: yes
  • C++20: no (has a user-declared constructor)

struct S {
 private:
    S() = default;
};
  • C++11: yes
  • C++14: yes
  • C++17: yes
  • C++20: no (has a user-declared constructor)

struct S {
    S() = delete;
};
  • C++11: yes
  • C++14: yes
  • C++17: yes
  • C++20: no (has a user-declared constructor)

struct S {
    S();
};
S::S() = default;
  • C++11: no (has a user-provided constructor)
  • C++14: no (...)
  • C++17: no (...)
  • C++20: no (has a user-declared constructor)

An out of line explicitly-defaulted definition counts as user-provided; recalling [dcl.fct.def.default]/4 (C++11) / [dcl.fct.def.default]/5 (C++14, C++17):

A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.


struct S {
    explicit S() = default;
};
  • C++11: yes
  • C++14: yes
  • C++17: no (has an explicit constructor)
  • C++20: no (has a user-declared constructor)

C++17 adds the requirement that an aggregate may not have an explicit constructor.


struct S {
    int x{42};
    S() = delete;
};
  • C++11: no (has a default member initializer)
  • C++14: yes
  • C++17: yes
  • C++20: no (has a user-declared constructor)

C++14 removed the requirement of C++11 for an aggregate to have no default member initializers.


struct S {
    int x{42};
    explicit S() = delete;
};
  • C++11: no (has a default member initializer)
  • C++14: yes
  • C++17: no (has an explicit constructor)
  • C++20: no (has a user-declared constructor)
like image 20
dfrib Avatar answered Nov 11 '22 01:11

dfrib