I alighted on this while permuting with a trivial piece of code:
struct Base0 {};
struct Base1 {};
template<typename... Ts>
struct Derived: Ts... {};
int main() {
Derived<Base0, Base1> d0 {Base0{}, Base1{}}; // OK
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
}
I thought both d0
and d1
should have resulted in a compilation error since I can't see how Derived
without any matching ctor takes ctor arguments as passed and flags d0
's compilation as fine.
There's probably something obvious I'm missing. What is it about the uniform initialisation that's making it pass ? Is it aggregate initialisation or something ? What's happening with the temporaries passed to the ctor ?
Using C++17 online compiler here
Edit
As asked, I'm providing a copy-paste of the spew-out:
main.cpp: In function ‘int main()’:
main.cpp:9:47: error: no matching function for call to ‘Derived::Derived(Base0, Base1)’
Derived<Base0, Base1> d1 (Base0{}, Base1{}); // ERROR
^
main.cpp:5:8: note: candidate: constexpr Derived::Derived()
struct Derived: Ts... {};
^~~~~~~
main.cpp:5:8: note: candidate expects 0 arguments, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(const Derived&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided
main.cpp:5:8: note: candidate: constexpr Derived::Derived(Derived&&)
main.cpp:5:8: note: candidate expects 1 argument, 2 provided
Looks like this is a new C++17 feature of aggregate initialisation:
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.
It comes with the change that a class with bases may now be an aggregate (as long as they are not virtual
, private
, or protected
… though they don't even need to be aggregates! 😲).
Your failing case does not use aggregate initialisation, but instead attempts a good old-fashioned constructor invocation. As you've identified, no such constructor exists.