Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is aggregate initialisation valid in C++11?

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?

like image 522
Clinton Avatar asked Jun 08 '11 01:06

Clinton


2 Answers

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.

like image 160
Kerrek SB Avatar answered Oct 11 '22 23:10

Kerrek SB


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.

like image 2
Puppy Avatar answered Oct 11 '22 23:10

Puppy