The reason for this is that the initializer list here
do_nothing({b1, b2});
is of a different type than
std::initializer_list<B*> blist = {b1, b2};
Since do_nothing
takes a std::initializer_list<A*>
the braced initialization list in your function call (do_nothing({b1, b2})
) is used to construct the std::initializer_list<A*>
from your function parameter. This works, because B*
is implicitly convertible to A*
. However, std::initializer_list<B*>
is not implicitly convertible to std::initializer_list<A*>
, hence you get that compiler error.
Lets write some pseudo-code to demonstrate what happens. First we take a look at the working part of the code:
do_nothing({b1, b2}); // call the function with a braced-init-list
// pseudo code starts here
do_nothing({b1, b2}): // we enter the function, here comes our braced-init-list
std::initializer_list<A*> l {b1, b2}; // this is our function parameter that gets initialized with whatever is in that braced-init-list
... // run the actual function body
and now the one that doesn't work:
std::initializer_list<B*> blist = {b1, b2}; // creates an actual initializer_list
do_nothing(blist); // call the function with the initializer_list, NOT a braced-init-list
// pseudo code starts here
do_nothing(blist): // we enter the function, here comes our initializer_list
std::initializer_list<A*> l = blist; // now we try to convert an initializer_list<B*> to an initializer_list<A*> which simply isn't possible
... // we get a compiler error saying we can't convert between initializer_list<B*> and initializer_list<A*>
Note the terms braced-init-list and initializer_list. While looking similar, those are two very different things.
A braced-init-list is a pair of curly braces with values in between, something like this:
{ 1, 2, 3, 4 }
or this:
{ 1, 3.14, "different types" }
it is a special construct used for initialization that has its own rules in the C++ language.
On the other hand, std::initializer_list
is just a type (actually a template but we ignore that fact here as it doesn't really matter). From that type you can create an object (like you did with your blist
) and initialize that object. And because braced-init-list is a form of initialization we can use it on the std::initializer_list
:
std::initializer_list<int> my_list = { 1, 2, 3, 4 };
Because C++ has a special rule that allows us to initialize each function argument with a braced-init-list, do_nothing({b1, b2});
compiles. This also works for multiple arguments:
void do_something(std::vector<int> vec, std::tuple<int, std::string, std::string> tup)
{
// ...
}
do_something({1, 2, 3, 4}, {10, "first", "and 2nd string"});
or nested initialization:
void do_something(std::tuple<std::tuple<int, std::string>, std::tuple<int, int, int>, double> tup)
{
// ...
}
do_something({{1, "text"}, {2, 3, 4}, 3.14});