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});