3

I'm a bit confused about how/why the constructors are called in C++11 and C++17.

#include <iostream>
using namespace std;

//---

template<typename T>
struct StructTest
{
public:
  const T Var = -1;

  //---

  // constexpr StructTest() = delete;  // 1
  // constexpr StructTest() = default; // 2

  // constexpr StructTest(const StructTest &Source) = delete;                  // 3
  // constexpr StructTest(const StructTest &Source) = default;                 // 4
  // constexpr StructTest(const StructTest &Source) {cout << "Copy" << endl;}; // 5
};

//---

StructTest<int> A{};
StructTest<int> A1{1};
StructTest<int> A2(A);

//---

int main(void)
{
  return(0);
};

So I'm confused by what happens when I uncomment some combination of lines (and compiling with the c++17 standard flag with clang):

  • 1, Compiles. List init's for A and A1, and the default copy constructor for A2
  • 2, Compiles. Default constructor for A and list init A1(?), and the default copy constructor for A2
  • 1 + 3 or 2 + 3, Fails to compile because of the deleted copy constructor for A2
  • 1 + 4, Compiles. Default constructor for A and list init A1(?), and the default copy constructor for A2
  • 2 + 4, Compiles. Default constructor for A and list init A1(?), and the default copy constructor for A2
  • 1 + 5, Fails to compile. Says A is missing(deleted) a default constructor, and no matching constructor for A1?
  • 2 + 5, Fails to compile. No matching constructor for A1?

I think I understand most of this, but I'm confused why the 1 + 5, and 2 + 5 compiles fail. Could anyone explain the logic the compiler is using to select the constructors that it's going to use, and why it fails to compile?

If the constructors that I think are being called in the other cases are wrong, could you also point out what is being called, and why?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Andy
  • 127
  • 1
  • 1
  • 8

3 Answers3

9

1, Compiles. List init's for A and A1, and the default copy constructor for A2

What you call List init in this case is actually aggregate initialization because StructTest is an aggregate. This is allowed because the presence of an explicitly defaulted or deleted constructor still makes the class an aggregate.

2, Compiles. Default constructor for A and list init A1?, and the default copy constructor for A2

A1 is aggregate initialized like what happened in 1. The rest it correct

1 + 3 or 2 + 3, Fails to compile because of the deleted copy constructor for A2

This is the expected behavior since the copy constructor is marked as deleted.

1 + 4, Compiles. Default constructor for A and list init A1?, and the default copy constructor for A2

Again, aggregate initialization for A and A1

2 + 4, Compiles. Default constructor for A and list init A1?, and the default copy constructor for A2

A and A1 will be aggregate initialized but it will use the default member initializer of Var when initializing A per [dcl.init.aggr]/5.1

1 + 5, Fails to compile. Says A is missing(deleted) a default constructor, and no matching constructor for A1?

5 is a user provided non defaulted or deleted constructor. This means StructTest is no longer an aggregate and you cannot aggregate initialize it anymore.

2 + 5, Fails to compile. No matching constructor for A1?

Same reason as 1 + 5

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Hi @NathanOliver, Thank you very much! I had missed the bit about the non default/deleted constructors and the agrigate classes. I do have one further question about your answer though, the 1+4 and 2+5 cases, do these both call exactly the same constructors? I'd tried to read the C++ reference again, and I'd started to convince myself that 1+4 performs agrigate initialisation for A and A1 (as the defaulr constructor is delete by line 1), but 2+4 calls the default constructor for A, and aggregate initialization for A1? – Andy Dec 05 '18 at 21:24
  • Note that what OP *calls List init* actually is list initialization. Indeed, it is aggregate initialization as well to be precise. Aggregate initialization is a form of list initialization. – eerorika Dec 05 '18 at 21:24
  • @Andy In 2 + 4 it is still aggregate initialized, it is just that it will use the default member initializer: https://timsong-cpp.github.io/cppwp/dcl.init.aggr#5.1 – NathanOliver Dec 05 '18 at 21:31
6

(This is additional information to the other answers)

The behaviour of this code is different for C++11, C++14/17, and C++20! Due to the changing definition of aggregate.

In C++11 the class was not an aggregate because it has a brace-or-equal-initializer (the = -1), so case 1 would not compile.

In C++14 and 17 , the class is an aggregate, the other answers cover this case.

In C++20 the class will not be an aggregate again because there is a new rule that any user-declared constructor disqualifies a class from being an aggregate; so case 1 will stop compiling again, and in case 2, StructTest<int> A1{1}; will not compile due to too many arguments to the constructor, etc.

M.M
  • 138,810
  • 21
  • 208
  • 365
3

What you refer to as list init is actually called aggregate initialization. Your class is an aggregate in all cases but when you uncomment line 5 - at which point it stops being an aggregate. An aggregate class is a class where all constructors are either defaulted (explicitly or implicitly) or deleted. You have only one non-defaulted, non-deleted constructor, so unless you uncomment this, your class remains an aggregate.

With this in mind, most of your example revolve around aggregate initialization, except the case where you explicitly prohibit copy by deleting copy-constructor or add non-defaulted copy-constructor and make the class non-aggregate.

More on aggregate and aggregate initialization: https://en.cppreference.com/w/cpp/language/aggregate_initialization

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Also related, [std::is_aggregate](https://en.cppreference.com/w/cpp/types/is_aggregate). – Eljay Dec 05 '18 at 21:21