13

What is it about having an aggregate public base class (or even multiple aggregate public base classes) that would make a class lose the nice properties of aggregate classes?

Definition of "aggregate base class" from http://en.cppreference.com/w/cpp/language/aggregate_initialization http://en.wikipedia.org/wiki/C++_classes#Aggregate_classes


The nice properties of aggregate classes:

  • Without defining a constructor, an aggregate type can be initialized by passing in a brace-enclosed list of values to initialize its members (or base classes, if they had allowed them).
  • Aggregate types are considered "simple" (a generalization of PODs), and can be used as a literal type for the purposes of constexprs.

Abridged example of initialization from http://en.cppreference.com/w/cpp/language/aggregate_initialization#Example:

#include <string>
#include <array>
struct S {
  int x;
  struct Foo {
    int i;
    int j;
    int a[3];
  } b;
};

int main()
{
  S s1 = { 1, { 2, 3, {4, 5, 6} } };
  S s2 = { 1, 2, 3, 4, 5, 6}; // same, but with brace elision
}

See also: What are Aggregates and PODs and how/why are they special?

Community
  • 1
  • 1
leewz
  • 3,201
  • 1
  • 18
  • 38
  • I'm guessing it's because you wouldn't be able to initialize the members of such an aggregate using an initializer list. – Praetorian Nov 07 '13 at 21:18
  • 2
    Which "nice properties" in particular are you concerned about? List them, give examples of how they should work with empty and non-empty base classes and virtual inheritance, and by the time you've done that you'll have your answer. Or else a much better question. – Ben Voigt Nov 07 '13 at 21:20
  • So now it seems your saying all it really gains you is having to write one less constructor, a constructor that will likely be only one line – aaronman Nov 07 '13 at 22:01
  • 1
    Aggregate types can be used as literal types (http://en.cppreference.com/w/cpp/concept/LiteralType) which can in turn be used for constexpr parameters and return values (http://en.cppreference.com/w/cpp/language/constexpr). constexpr functions can be used to make compile-time constants. But for the sake of the question, does it really matter WHY I'd want it? – leewz Nov 07 '13 at 22:04
  • @leewangzhong I like the constexpr thing though I guess I've used it without thinking about it, keep in mind constexprs will be way more versatile in c++14 – aaronman Nov 08 '13 at 02:21
  • Looks like a bug/feature of the standard to me. In other words, the standard could be different in this respect, but that may be harder to implement and therefore the committee couldn't be bothered. – Walter Nov 12 '14 at 09:25

4 Answers4

4

According to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3308.pdf

Despite the clear intentions of the standard, type B is not a literal type:

struct A {}; struct B : A {};

This is because its constructor is not implicitly defined until it is odr-used, and until that point it has no constexpr constructors.

Honestly not sure what this means, though.

leewz
  • 3,201
  • 1
  • 18
  • 38
3

This is the definition of an aggregate from the c++11 standard, that is really all I can give without trying to guess what the committee was thinking when they made this decision.

1 An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal- initializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

In the bold it says that an aggregate has no base classes.

As for the issue with inheritance that the other answer brings up you can make uniform initialization work with inheritance. NOTE: A is still an aggregate.

struct A {                                                                         
    int val_A;                                                                                                                        
};                                                                                 

struct B : public A {                                                              
    int val_B;                                                                     
    B(int a, int b) : A{a}, val_B(b) {}                                            
};                                                                                 
int main() {                                                                       
    B b {2,3};                                                                     
    return 0;                                                                      
}  

You just have give B a constructor, IMO the standard could have just as easily chosen this as the default. Aggregates were probably kept in because they were in previous standards but the truth is with c++11 features you really don't even need them. In fact one issue is that std::array needs double braces since it has no initializer list constructor, I think this issue is solve in c++14.

I would like to add that I don't see how much being an aggregate adds to a class given the new features of uniform initialization and initializer lists.

aaronman
  • 18,343
  • 7
  • 63
  • 78
  • 1
    He's asking *why* have a base class (which itself is an aggregate) would cause a class to stop being an aggregate – Praetorian Nov 07 '13 at 21:18
  • 1
    @Praetorian the standard didn't have to make this decision, it says nothing about why. I understand that I haven't answered the full question but what else can I say – aaronman Nov 07 '13 at 21:18
  • 6
    Then your answer might as well be rephrased as *because the standard says so*. I'm not saying that that would necessarily be the wrong answer, just that your answer, as currently written, skirts the question completely. – Praetorian Nov 07 '13 at 21:20
  • @Praetorian fair enough I changed it – aaronman Nov 07 '13 at 21:22
  • -1: Sorry, I know you tried, but this simply doesn't answer the question posed. Perhaps it's unanswerable, but then I'd rather see no answers. – Lightness Races in Orbit Nov 07 '13 at 21:30
  • @LightnessRacesinOrbit I agree but the other answer is just a guess, it could be totally incorrect – aaronman Nov 07 '13 at 21:32
  • @LightnessRacesinOrbit I tried to add a little more to the answer to explain things better, but I still can't say anything definitive – aaronman Nov 07 '13 at 22:00
  • @aaronman I added in a reason why aggregate types are still relevant, or more relevant due to new features. – leewz Nov 08 '13 at 01:58
3

Since C++17 classes that have public, non-virtual base classes can be aggregates.

struct Base1 {
 int a, b;
};
struct Base2 {
 int c;
};

struct Foo : Base1, Base2 {
    int d, e;
};

Objects of struct Foo can be aggregate-initialized. The following initializations are equivalent:

Foo foo {Base1{1, 2}, Base2{3}, 4, 5};
Foo foo {{1, 2}, {3}, 4, 5};
Foo foo {1, 2, 3, 4, 5};
Kaznov
  • 1,035
  • 10
  • 17
  • I'm bound to C++14 and eventually used aggregation instead of inheritance because it didn't play a role in my use case: `struct Foo { Base1 base1; Base2 base2; int d, e; };` and I can use the three initialization variants stated here (with a missing brace warning in the latest variant of course). – Roland Sarrazin Aug 26 '21 at 09:43
  • On a side note, following my previous comment, if I want to access a, b and c I have to `foo.base1.a; foo.base1.b; foo.base2.c;`. To make that more usable (or to deal with legacy code) I have added aliases, i.e. `struct Foo { Base1 base1; Base2 base2; int d, e; int& a{base1.a}; int& b{base1.b}; int& c{base2.c}; };` and I can then `foo.a; foo.b; foo.c;` as with the inheritance version. This causes of course higher maintenance efforts but can be a viable solution in some use cases like mine. – Roland Sarrazin Aug 26 '21 at 09:46
2

How would you initialize the base class?

Derived d = {{1, 2}, 3, 4};

Or

Derived d = {1, 2, 3, 4};

Is

Derived d = {3, 4};

permitted?

To avoid all of this, no base classes.

  • except i'm pretty sure a derived class can use uniform initialization syntax, which ends up looking the same – aaronman Nov 07 '13 at 21:19
  • 1
    @aaronman: I'm pretty sure it can't unless you write a constructor, in which case that controls the order of arguments and how many get passed along to the base class(es). – Ben Voigt Nov 07 '13 at 21:21
  • 2
    Let's say that the base class was moved to be the first member. How would you initialize the first member of an aggregate class? I would say that question has the same answer. – leewz Nov 07 '13 at 21:35
  • Also, I'm pretty sure the second can usually be substituted for the first (int a[2][2] = {1,2,3,4} is legal, AFAIK). – leewz Nov 07 '13 at 21:41
  • @leewangzhong: Not in the old standard. Not sure about C++11, but I doubt it. –  Nov 08 '13 at 14:22
  • It definitely is in [C](http://stackoverflow.com/questions/18157251/multidimensional-array-initialization-in-c) and [C++11](http://stackoverflow.com/questions/11734861/when-can-outer-braces-be-omitted-in-an-initializer-list/11735045#11735045). I believe it was even before C++11. – leewz Nov 08 '13 at 15:51