It looks like when I create a std::initializer_list<B*>
where class B
is derived from class A
and pass this to a function which accepts a std::initializer_list<A*>
, the compiler gets confused. If, however, I create a std::initializer_list<B*>
in-place with brace initializers (which I'd assume makes a temporary std::initializer_list
) it's able to convert it just fine.
Specifically it seems to be unable to convert std::initializer_list<B*>
to std::initializer_list<A*>
, even though some conversion clearly exists as evidenced by the one working function call here.
What's the explanation for this behavior?
#include <initializer_list>
#include <iostream>
class A {
public:
A() {}
};
class B : public A {
public:
B() {}
};
using namespace std;
void do_nothing(std::initializer_list<A*> l) {
}
int main() {
B* b1;
B* b2;
std::initializer_list<B*> blist = {b1, b2};
//error, note: candidate function not viable: no known conversion from
//'initializer_list<B *>' to 'initializer_list<A *>' for 1st argument
//do_nothing(blist);
//Totally fine, handles conversion.
do_nothing({b1, b2});
return 0;
}
Try it here.
edit:
As a workaround doing something like this
std::initializer_list<A*> alist = {b1, b2};
seems to be accepted by do_nothing()
but I'm still curious about the behavior.
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});
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