When is aggregate initialisation valid in C++11?
Asked Answered
F

2

10

Lets say we have the following code:

#include <iostream>
#include <string>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
  std::string s;
};

struct B
{
  A a;
};

int main()
{
  B{A()};
}

Here, I believe struct A is not an aggregate, as it has both non-trivial constructors and also a std::string member which I presume is not an aggregate. This presumably means that B is also not an aggregate.

Yet I can aggregate initialize B. In addition this can be done without either the copy nor move constructor being called (e.g. C++0x GCC 4.5.1 on ideone).

This behavior seems like a useful optimization, particularly for composing together large stack types that don't have cheap moves.

My question is: When is this sort of aggregate initialization valid under C++0x?

Edit + follow up question:

DeadMG below answered with the following:

That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.

Note that when I change B to the following:

struct B
{
  A a;
  B(const A& a_) : a(a_) {}
  B(A&& a_) : a(std::move(a_)) {}
};

A move is performed.

So if this is just uniform initialization and just calling the constructor and doing nothing special, then how do I write a constructor that allows the move to be elided?

Or is GCC just not eliding the move here when it is valid to do so, and if so, is there a compiler and optimization setting that will elide the move?

Freespoken answered 8/6, 2011 at 1:56 Comment(10)
What do you mean by "eliding the move"? Move-construction is already a very cheap operation when done right, why do you want to elide it? In your example, you could write a constructor for B that takes the relevant parameters for A so you can construct B::A in B's initializer list, but what exactly is your concern?Selfdeception
Nice question. Interested to see how it turns out.Poisoning
@Kerrek SB: How do you make move construction cheap for std::array<int, 1000>?Freespoken
@Clinton: By using RVO and NRVO, effectively.Purificator
@Clinton: std::array is intended as a simple aggregate type which has no custom constructors. Hence all its constructors are automatically generated, and a simple array<int, 1000> newarray(move(oldarray)) should do the right thing (check the assembly yourself if you're unsure).Selfdeception
@DeadMG: If so, could you give an example in your answer of a code/compiler combination that has a class A with a non-default move and copy constructor, a class B with a member A and that constructs B from A using a non-default constructor without calling A's move or copy constructor?Freespoken
@Kerrek SB: Perhaps std::array was a bad example. Consider a large class which is not a simple aggregate.Freespoken
@Clinton: What is the question now? How to initialize a large, complicated class? Well, you can use initializer lists as constructor arguments now, which could help. Give an example if you have something specific in mind.Selfdeception
@Kerrek SB: Your answer below answers my question. Sorry for the confusion.Freespoken
I think an aggregate class must have no constructors and have all non-static data members public. Containing non-aggregate types is not a disqualifier. A is disqualified due to its constructors; containing a string isn't a reason. B is an aggregate, since A not being one isn't a disqualifier.Mcneal
S
6

According to the new standard, clause 8.5.1 (Aggretates), a sufficiently simple type (e.g. no user-defined constructors) qualifies as an aggregate. For such an aggregate Foo, writing Foo x{a, b, ... }; will construct the members from the list items.

Simple example:

struct A
{
  std::unordered_map<int, int> a;
  std::string b;
  std::array<int,4> c;
  MyClass d; // Only constructor is MyClass(int, int)
};

// Usage:
 A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, MyClass(4,4)};
// Alternative:
 A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, {4,4}};

The object x is constructed with all the relevant constructors executed in place. No maps or strings or MyClasses ever get copied or moved around. Note that both variants at at the bottom do the same thing. You can even make MyClass's copy and move constructors private if you like.

Selfdeception answered 8/6, 2011 at 2:44 Comment(0)
P
2

That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.

Purificator answered 8/6, 2011 at 2:0 Comment(3)
@Clinton: The optimizing capabilities of a web compiler are probably quite limited and you've just confused it.Purificator
What do you mean by 'uniform initialization'? Is this terminology used in the FDIS?Aware
The term has been around for some time. It means that you can use the same syntax, Foo x{a,b,c};, in all sorts of situations: initialize arrays, aggregates, or call specific user-defined constructors. The actual wording "uniform initialization" isn't actually in the FDIS as such, but if you read the section on initialization (8.5) you will see that all initialization needs can be met by some form of curly braces.Selfdeception

© 2022 - 2024 — McMap. All rights reserved.