列表初始化是一种隐式转换吗?

8
#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <vector>

class Base{
public:
    virtual ~Base() {}

};

class Derived: public Base { };

int main(){

    int arr[10];
    Derived d;
    Base *p = &d;

    std::map<std::type_index, std::string> proper_name = {
        {typeid(int), "int"}, {typeid(double), "double"}, {typeid(float), "float"}, {typeid(char), "char"},
        {typeid(Base), "Base"}, {typeid(Derived), "Derived"}, {typeid(std::string), "String"},
        {typeid(int[10]), "Ten int Array"}, {typeid(p), "Base Pointer"}};

}

我试图理解在这个列表初始化中发生的隐式转换。从N3337的13.3.1.7中得知:
当非聚合类类型T的对象进行列表初始化(8.5.4)时,重载决议会分两个阶段选择构造函数: 1. 最初,候选函数是类T的初始化器列表构造函数(8.5.4),参数列表由初始化器列表作为单个参数组成。 2. 如果没有可行的初始化器列表构造函数,则再次执行重载决议,其中候选函数是类T的所有构造函数,参数列表由初始化器列表的元素组成。

8.5.4:

如果一个构造函数的第一个参数是类型为std::initializer_list<E>或者是可能带有cv限定符的std::initializer_list<E>的引用,其中E是某种类型,且除此之外没有其他参数或者所有其他参数都有默认值,则该构造函数是初始化列表构造函数。
因此,std::map的以下构造函数列表表示: map (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
是候选函数,在这种情况下,value_typepair<const type_index, std::string>。最后来自13.3.3.1.5
如果参数类型为std::initializer_list<X>或“X数组”135,并且初始化列表的所有元素都可以隐式转换为X,则隐式转换序列是将列表中的元素转换为X所需的最差转换。
只要花括号列表的元素可以隐式转换为pair<const type_index, std::string>,就是有效的转换。但是这些元素本身也是花括号列表。 Pair不采用初始化器列表构造函数,从这里链接看起来从花括号初始化列表进行复制初始化使用13.3.1.7的第二部分来构造对象。因此以下内容:
pair<const type_index, std::string> p = {typeid(int), "int"}

变成:

pair<const type_index, std::string> p(typeid(int), "int")

但这是否被视为隐式转换?如何将使用两个参数的构造函数视为隐式转换?标准对此有何评论?
1个回答

3
你的结论是:
pair<const type_index, std::string> p = {typeid(int), "int"};

变成

pair<const type_index, std::string> p(typeid(int), "int");

这并不准确,因为第一个语句是复制列表初始化,而第二个是直接初始化。两者是相同的,唯一的区别在于如果选择了explicit构造函数,则复制列表初始化是非法的(并且在前者中不允许缩小转换)。

所以,如果涉及的pair构造函数被定义为

template<class U1, class U2>
explicit constexpr pair(U1&& x, U2&& y);
直接初始化仍然可成功,但复制列表初始化会失败。引用下文是[over.match.list]的部分:

在复制列表初始化中,如果选择了一个explicit构造函数,则初始化无效。


除此之外,你所说的一切都是正确的。因为pair没有初始化器列表构造函数,所以pair构造函数是一种隐式转换,并且根据[over.match.list]的第二条规则进行重载解析。

2
挑剔一下:「这两个是相同的,除非选择了显式构造函数,否则复制列表初始化会失效」,除非初始化列表仅有一个元素,且该元素的类型与正在初始化的对象的类型相同或派生自该类型(如果有initializer_list构造函数,这可能会例外于例外之外)。 - T.C.
@T.C. 谢谢,我之前不知道这两个。但是现在第一个问题已经解决了,我说的还是正确的,对吧?两个区别就是显式构造函数和缩小转换。 - Praetorian
是的,我认为现在是这样的。Copy-list和copy-vanilla有更大的不同。 - T.C.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接