我的编译器为何不知道这种转换,何时存在?

当我创建一个std::initializer_list<B*>来自class B的{​​{1}}并将其传递给接受class A的函数时,编译器会感到困惑。但是,如果我用括号初始化器在原地创建一个std::initializer_list<A*>(我认为它是一个临时std::initializer_list<B*>),则可以很好地进行转换。

特别是,似乎显然无法将std::initializer_list转换为std::initializer_list<B*>,尽管这里存在一个工作函数调用证明了某些转换。

对此行为有何解释?

std::initializer_list<A*>

Try it here.

编辑:

作为一种解决方法,请执行以下操作 #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; } std::initializer_list<A*> alist = {b1,b2};似乎接受了,但我仍然对此行为感到好奇。

happyboy_1 回答:我的编译器为何不知道这种转换,何时存在?

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

do_nothing({b1,b2});

的类型不同
std::initializer_list<B*> blist = {b1,b2};

由于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

现在不起作用了:

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*>

请注意术语 braised-init-list initializer_list 。虽然看起来很相似,但这是两个截然不同的事情。

braced-init-list 是一对花括号,中间有两个值,如下所示:

{ 1,2,3,4 }

或者这个:

{ 1,3.14,"different types" }

它是用于初始化的特殊结构,它具有C ++语言自己的规则。

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

std::initializer_list<int> my_list = { 1,4 };

因为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,4},{10,"first","and 2nd string"});

或嵌套初始化:

void do_something(std::tuple<std::tuple<int,std::string>,int,int>,double> tup) 
{ 
    // ...
}

do_something({{1,"text"},{2,3.14});
,

我们必须区分std::initializer_list<T>对象和 braced-init-list

Braced-init-list 用大括号括起来,用逗号分隔,并且是源代码级的构造。可以在各种上下文中找到它们,并且它们的元素不必都属于同一类型。例如

std::pair<int,std::string> = {47,"foo"};  // <- braced-init-list

当执行包含 braised-init-list 的语句时,有时会根据std::initializer_list<T>对象(对于某些T) >括号初始列表。这些上下文是:

  • 当声明为std::initializer_list<T>类型的变量具有 braced-init-list 作为其初始化程序时;和
  • 当对象以auto类型声明并从非空的 braced-init-list 复制初始化时。

如果无法从 braced-init-list 创建std::initializer_list<T>对象,则会发生编译错误。例如

std::initializer_list<int> l = {47,"foo"};  // error

除了上述创建std::initializer_list对象的方法外,还有另一种方法:通过复制现有对象。这是一个浅表副本(它只是使新对象指向与旧对象相同的数组)。当然,副本不会更改类型。


现在返回您的代码。当您尝试使用参数do_nothing调用blist函数时,您尝试做的事情是不可能的,因为您提供的内容不能用于创建std::initializer_list<A*>对象。创建此类对象的唯一方法是从 braised-init-list 或现有的std::initializer_list<A*>对象。

但是,将{b2,b1}作为参数可以正常工作,因为它是 braced-init-list 。允许从 braced-init-list 的元素到所需元素类型的隐式转换。

,

std::initializer_list是模板。

通常在C ++中,即使some_template<T>some_template<U>相关,TU也无关。

在调用do_something({b1,b2})中,编译器实际上创建了std::initializer_list<A*>(在应用list-initialization规则之后)。

您可以将do_nothing模板化为其他类型:

template<typename T,typename = std::enable_if_t<std::is_convertible<T,A>::value>>
void do_nothing(std::initializer_list<T*> l) {

}

(可选的SFINAE部分将T限制为可转换为A的类型)

有关更多可能的解决方案,请参考this related question

本文链接:https://www.f2er.com/2755897.html

大家都在问