7
votes

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
1
What are the actual errors you get from the example you show? Please copy-paste (as text) the full and complete output into the question, as it might have some notes that could give hints about the problem. - Some programmer dude
@Someprogrammerdude pasted the error now. Seems I got an insta-downvote, so I'm assuming it is indeed something obvious i'm missing :D - ustulation
@ustulation Instadownvote for no minimal reproducible example or error in the question is fairly usual. Most of us don't have time to keep asking for details in the question on every single question (which is seemingly what would otherwise be required!) - Lightness Races in Orbit
Ah fair enough, been a long time since i posted. Good reminder ! - ustulation
Note that C++20 does allow the syntax with parentheses, partly because it is that syntax that is used by forwarding functions like make_unique or emplace. - Davis Herring

1 Answers

10
votes

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.