为什么我的编译器无法识别出这种转换,它何时存在?

bre*_*nzo 4 c++ casting initializer-list

看起来当我创建一个std::initializer_list<B*>where class Bis 派生class A并将其传递给接受 a 的函数时std::initializer_list<A*>,编译器会感到困惑。但是,如果我创建一个std::initializer_list<B*>带有大括号初始值设定项的就地(我假设它是一个临时的std::initializer_list),它就可以很好地转换它。

具体来说,它似乎无法转换std::initializer_list<B*>为std::initializer_list<A*>,即使某些转换明显存在,如此处的一个工作函数调用所证明的那样。

这种行为的解释是什么?

#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;
}
Run Code Online (Sandbox Code Playgroud)

在这里尝试一下。

编辑:

作为一种解决方法,做这样的事情 std::initializer_list<A*> alist = {b1, b2}; 似乎被接受do_nothing(),但我仍然对这种行为感到好奇。

Tim*_*imo 5

这样做的原因是这里的初始化列表

do_nothing({b1, b2});
Run Code Online (Sandbox Code Playgroud)

属于不同类型

std::initializer_list<B*> blist = {b1, b2};
Run Code Online (Sandbox Code Playgroud)

由于函数调用 ( ) 中do_nothing采用大括号初始化列表,因此用于从函数参数构造 。这是可行的,因为它可以隐式转换为. 但是,不能隐式转换为,因此您会收到编译器错误。std::initializer_list<A*>do_nothing({b1, b2})std::initializer_list<A*>B*A*std::initializer_list<B*>std::initializer_list<A*>

让我们编写一些伪代码来演示会发生什么。首先我们看一下代码的工作部分:

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
Run Code Online (Sandbox Code Playgroud)

现在是行不通的:

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*>
Run Code Online (Sandbox Code Playgroud)

请注意术语“braced-init-list”和“initializer_list”。虽然看起来很相似,但这是两个截然不同的东西。

花括号初始化列表是一对花括号,其间有值,如下所示:

{ 1, 2, 3, 4 }
Run Code Online (Sandbox Code Playgroud)

或这个:

{ 1, 3.14, "different types" }
Run Code Online (Sandbox Code Playgroud)

它是一种用于初始化的特殊构造,在 C++ 语言中有自己的规则。

另一方面,std::initializer_list它只是一种类型(实际上是一个模板,但我们在这里忽略这个事实,因为它并不重要)。从该类型,您可以创建一个对象(就像您对 所做的那样blist)并初始化该对象。因为braced-init-list是初始化的一种形式,我们可以在std::initializer_list:

std::initializer_list<int> my_list = { 1, 2, 3, 4 };
Run Code Online (Sandbox Code Playgroud)

因为 C++ 有一个特殊的规则,允许我们使用braced-init-list初始化每个函数参数,do_nothing({b1, b2});因此可以编译。这也适用于多个参数:

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"});
Run Code Online (Sandbox Code Playgroud)

或嵌套初始化:

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});
Run Code Online (Sandbox Code Playgroud)