initializer list constructor error with CRTP - c++

I'm wetting my feet with C++11 and am really confused why this doesn't work:
template <class T>
struct A {
size_t size() const { return sizeof(T); }
};
struct B : A<B> {
int x;
int y;
};
B var {1, 5};
I'm using gcc 4.8.2 and get an error saying:
no matching function for call to 'B(<brace-enclosed initializer list>)'
It works just fine when I don't derive from A, so does the derivation somehow change the POD-ness of my struct B?

Aggregate-initialization requires your type to be an aggregate. An aggregate cannot have base classes:
An aggregate is an array or a class (Clause 9) with no user-provided
constructors (12.1), no private or protected non-static data members
(Clause 11), no base classes (Clause 10), and no virtual functions
(10.3).

Related

Aggregate class and non-viable constructor template

As we know, in C++20 a class that has a user-declared constructor is not aggregate.
Now, consider the following code:
#include <type_traits>
template <bool EnableCtor>
struct test {
template <bool Enable = EnableCtor, class = std::enable_if_t<Enable>>
test() {}
int a;
};
int main() {
test<false> t {.a = 0};
}
Neither GCC nor CLang compile this code. So, the class is not an aggregate, despite the fact that there is no instantiated constructor here.
Is the constructor template considered to be a "declared constructor"? What does the Standard say about such a situation?
The definition of aggregate has changed a lot, but the relevant part here has been pretty constant. The C++20 wording in [dcl.init.aggr] says:
An aggregate is an array or a class ([class]) with
no user-declared or inherited constructors ([class.ctor]),
no private or protected direct non-static data members ([class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
Note that it just says no declared constructors, period. Not something subtle about whether or not the constructor is viable for a particular specialization of a class template. Just none at all.
So given something like this:
template <bool B>
struct X {
int i;
X(int i) requires B;
};
X<false> still isn't an aggregate, even though its only declared constructor isn't viable for that specialization. Doesn't matter. Aggregate-ness is a property of declarations only.

Using initializer lists through inheritance

Is there a way to use initializer lists to initialize Bar?
struct Foo {
int i[2];
};
struct Bar : Foo {};
Foo f{0, 1}; // OK
Bar b{0, 1}; // error: no matching function for call to
// ‘Bar::Bar(< brace-enclosed initializer list>)’
Foo is an aggregate, and aggregates can be initialised using braced-enclosed lists performing aggregate initialisation. That is why Foo can be initialised using {0, 1} even without a constructor.
However, whether Bar is also an aggregate depends on the version of C++ which you're using. In C++14 and earlier, aggregates cannot have base classes, so Bar is not an aggregate. It therefore cannot be initialised using aggregate initialisation and would need an appropriate constructor. [Live example]
In C++17, the definition of "aggregate" was broadened to also include classes with non-virtual public base classes, so in C++17, Bar is an aggregate and indeed, can be initialised using aggregate initialisation: [Live example]
Since Foo is an aggregate type it's being initialized through aggregate initialization, which does not support base classes initialization as of c++14. But the good news are that it will be supported in c++17 :)
Example taken from cppreference:
// aggregate
struct base1 { int b1, b2 = 42; };
// non-aggregate
struct base2 {
base2() : b3(42) {}
int b3;
};
// aggregate in C++17
struct derived : base1, base2 { int d; };
derived d1{ {1, 2}, { }, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4
derived d2{ { }, { }, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4
It compiles, but with C++17 where aggregates can have base classes. This allows for list initialization of base data members. see here for more:
http://en.cppreference.com/w/cpp/language/aggregate_initialization
The effects of aggregate initialization are:
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the
class definition, is copy-initialized from the corresponding clause of
the initializer list.

Enable default initializer list constructor

I believe modern C++ initializer lists are very useful for initializing objects, to the point of removing the need for defining your own constructor:
struct point
{
float coord[3];
};
point p = {1.f, 2.f, 3.f}; // nice !
However, this doesn't work when my class inherits from another class:
template<typename T>
class serializable
{
protected:
serializable() = default;
...
// other stuff
}
struct point : public serializable<point>
{
float coord[3];
};
point p = {1.f, 2.f, 3.f}; // Doesn't work :(
I tried adding point() = default; to my point class, but that didn't work either. How can I still initialize point with an initializer list?
Your original case relied upon aggregate initialization [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if T is an aggregate, aggregate initialization is performed
Where an aggregate and aggregate initialiazation are, from [dcl.init.aggr], emphasis mine:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list
are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each
member is copy-initialized from the corresponding initializer-clause.
But now, since point has a base class (serializable<point>), point is no longer an aggregate and no longer supports aggregate initialization.
The solution is to simply provide such a constructor to initialize point:
struct point : public serializable<point>
{
template <typename... T>
point(T... ts)
: coord{ts...}
{ }
float coord[3];
};

Why do non-static data member initializers defeat uniform initialization syntax?

If all of your class/struct data members lack initializers, you can use uniform initialization syntax to construct the object.
struct foo
{
int i;
float f;
};
...
foo bar{ 5, 3.141f };
But if one or more members have initializers, uniform initialization syntax becomes invalid.
struct foo
{
int i;
float f = 0;
};
...
foo bar{ 5, 3.141f }; // Compiler error.
I surmise that the addition of a data member initializer automatically implements one or more default constructors and suppresses the default implementation of the initialization_list constructor. Is that the intended standard? Why does it work this way?
Yes, this is intended by the standard. What you are attempting here is aggregate initialization. Unfortunately, your second foo is no longer considered an aggregate due to the equal initializer of f. See 8.5.1 [dcl.init.aggr] (emphasis mine):
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equalinitializers 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).
Because you have an equal initializer for the member f, you will need to provide a custom constructor to support the syntax you are after:
struct foo
{
int i;
float f = 0;
constexpr foo(int i, float f) : i(i), f(f) { }
};
...
foo bar{ 5, 3.141f }; // now okay
As to why this was specified in the standard, I have no idea.

Constructor for Struct with Private Members

Consider the following code:
class A
{
private:
struct B { private: int i; friend class A; };
public:
static void foo1()
{
B b;
b.i = 0;
}
static void foo2()
{
B b = {0};
}
};
Why does foo1 work but not foo2? Is not the struct initializer constructor visible for class A? Is there anyway to make this work in C++11?
(Note, removing the private makes foo2 working.)
Why does foo1 work but not foo2? Is not the struct initializer constructor visible for class A?
B b = {0};
Does not work because B is not an Aggregate. And it is not an Aggregate because it has an non-static private data member. If you remove the private specifier, B becomes an Aggregate and hence can be initialized in this manner.
C++03 Standard 8.5.1 Aggregates
Para 7:
If there are fewer initializers in the list than there are members in the aggregate, then each member not explicitly initialized shall be value-initialized (8.5).
[Example:
struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };
initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of an expression of the form int(), that is,0. ]
C++03 standard 8.5.1 §1:
An aggregate is an array or a class (clause 9) with no user-declared
constructors (12.1), no private or protected non-static data members (clause 11),
no base classes (clause 10), and no virtual functions (10.3).