I am hoping someone can give me the technical details of why the following will not compile, and if possible, a work around.
I have an existing struct called Foo, and code which uses initializer lists to create instances of Foo. This code compiles and works:
struct Foo {
int id1;
int id2;
};
int main()
{
Foo f({1,2});
return f.id1;
}
I would like Foo to implement an interface going forward:
struct Interface {
// All pure virtual methods, but this won't compile even if empty
};
struct Foo : public Interface{
int id1;
int id2;
};
int main()
{
Foo f({1,2});
return f.id1;
}
This code no longer compiles, with errors in the vein of
cannot convert argument 1 from 'initializer list' to 'const _Ty &'
(Error changes depending on your exact compiler.)
I have found this section of the standard relating to aggregate initialization:
[dcl.init.aggr]/1 An aggregate is an array or a class (Clause 12) with 1.1 no user-provided, explicit, or inherited constructors (15.1), 1.2 no private or protected non-static data members (Clause 14), 1.3 no virtual functions (13.3), and 1.4 no virtual, private, or protected base classes (13.1).
Though I am not actually sure if aggregate initialization is what's occurring here. Can someone explain the error that's occurring, and if possible, offer changes I could make to the interface? I have several existing structs which need this interface, and lots of existing code which uses this form of initialization, and I'd like to rewrite as little of it as possible. Thank you!
You need to initialize the base class even though it is empty:
Foo f({{},1,2});
see it live on godbolt
Further down in the standard in the section you are referring to we can see an example of this in [dcl.init.aggr]p4.2:
struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; derived d2{{}, {}, 4};
initializes d1.b1 with 1, d1.b2 with 2, d1.b3 with 42, d1.d with 4, and d2.b1 with 0, d2.b2 with 42, d2.b3 with 42, d2.d with 4. —end example]
Also see [dcl.init.aggr]p2 which explains what the elements of an aggregate are:
The elements of an aggregate are:
-for an array, the array elements in increasing subscript order, or
-for a class, the direct base classes in declaration order, followed by the direct non-static data members ([class.mem]) that are not members of an anonymous union, in declaration order.
and [dcl.init.aggr]p3 says:
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate. ...
Note, the answer assumes C++17 or greater since before C++17 an aggregate was not allowed to have a base class.
@ShafikYaghmour explained why when Interface
is empty, aggregate initialization can not be done as it used to be.
But if Interface
has virtual functions, as suggested in the question, the derived class from Interface
will not be an aggregate. So the class that implements Interface
and holds data members as Foo
must implement a constructor. The simplest way I see (which, depending on the "triviality" of the data members, might not be the most efficient in term of speed) is this:
struct Interface {
// All pure virtual methods, but this won't compile even if empty
virtual void bar() =0;
};
struct Foo_data{ //change the name of the aggregate
int id1;
int id2;
};
struct Foo
:Interface //Foo has virtual function => Foo is not an aggregate
,Foo_data
{
Foo() =default;
Foo(Foo_data data):Foo_data(std::move(data)){}//a constructor must be provided
void bar() override {}
};
int main(){
Foo f({1,2});
return f.id1;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With