initializer_list的实现如何改变编译器读取的方式?



我刚刚了解到,我们可以使用标准库类型initializer_list<T>来实现用{}分隔的T类型元素列表初始化类实例。例如

class X
{
public:
X(initializer_list<int> lst)
{
cout << "initializer-list constructorn";
}
X(int i, int j, int k)
{
cout << "constructor taking 3 intsn";
}
};
int main()
{
X a{ 1,2,3 };  // initializer-list constructor
}

据我所知,当编译器在上面的代码中看到{ 1,2,3 }时,它将首先寻找一个接受initializer_list<int>的构造函数,并且因为有一个,它将从{ 1,2,3 }构造一个initializer_list<int>对象。如果没有接受initializer_list<int>的构造函数,编译器将生成与X a(1,2,3)相同的代码。

如果我上面描述的过程是正确的,对我来说有一些"魔法";我不明白为什么定义一个类型(initializer_list)并让函数(初始化列表构造函数)接受该类型的对象(比如上面的lst)可以"改变"。调用函数的方式。我的意思是:编译器现在可以理解{}表示法,所以我们不需要X a({1,2,3})这样的东西,在这种情况下,当没有初始化列表构造函数时,编译器更喜欢初始化列表构造函数而不是其他构造函数,而将{}表示法视为()表示法。

更重要的是,我可以定义一个类型(比如,我自己的initializer_list),这样当编译器看到一个类的特殊形式的实例化(比如,{}初始化列表)时,它会在这个类中搜索一个构造函数,这个构造函数接受我自己的initializer_list类型的对象,如果这个构造函数不存在,它会以其他方式处理这个特殊形式的实例化?

换句话说:

class X
{
public:
X(initializer_list<int> lst)
{
cout << "stdn";
}
X(my_initializer_list<int> lst)
{
cout << "user-definedn";
}
};
int main()
{
X b{ 1,2,3 };
}

我可以在全局作用域中实现my_initializer_list以便X b{ 1,2,3 };调用X::X(my_initializer_list<int> lst)吗?


我目前的猜测是,这是语言本身的一个特性,当编译器看到{}初始化列表时,编译器将总是首先寻找接受std::initializer_list的构造函数,而不是在任何其他命名空间中定义的initializer_list(我的意思是,编译器的这种行为在std::initializer_list中没有实现,但这是一个设计到编译器中的功能)。请问我的猜测是否正确?

没有魔法。这是一种初始化列表类型,其规则可在这里获得:https://en.cppreference.com/w/cpp/language/list_initialization.

我可以在全局作用域中实现my_initializer_list,以便X b{1,2,3};调用X::X(my_initializer_list lst)?

不行,这个不行。阅读上面的规则——对于初始化器列表有一个特殊的处理。如果没有接受初始化列表的构造函数,则选择与实参列表匹配的构造函数。

要调用X::X(my_initializer_list<int> lst),必须执行X b{my_initializer_list{1,2,3 }};

在创建类的实例时如何解释大括号{}是一种语言特性。也就是说,我们不能更改它,以便下次遇到它们时,应该选择自定义的my_initializer_list构造函数。我们能控制的是一旦它被选中(假设它确实被语言规则选中了)会发生什么。

我可以在全局范围内实现my_initializer_list,以便X b{ 1,2,3 };调用X::X(my_initializer_list<int> lst)吗?

因此,据我所知,我不认为你的建议是可能的/可行的,因为它需要{}被不同的解释。

想象一下,如果你提供了多个my_initializer_list,比如my_initializer_list1,my_initializer_list2,my_initializer_list3,假设你的类有一个对应于它们的构造函数,那么当你使用{}来创建类的实例时,如何决定选择这些构造函数中的哪一个呢?

最新更新