这是一个非常有趣的
列表初始化
案例。
bar({1, 2, 3})
bar({{1, 2, 3}})
这是一个复制列表初始化的案例,通过复制列表初始化创建了一个临时对象,并将const引用绑定到它上面。
要理解
bar()
函数调用的工作原理,需要了解列表初始化的工作方式以及其重载解析规则。因此,让我们逐一进行解释。
std::vector<double> vec = {0, 1};
这也是列表初始化(复制列表初始化),
std::vector
有以下构造函数。
vector( std::initializer_list<T> init,
const Allocator& alloc = Allocator() );
"
std::initializer_list<T>
构造函数在重载决议中比其他构造函数优先级更高,因此重载决议会选择它。
另一种情况,
"
std::vector<double> vec = {0, 1}
std::vector<double> other = {vec}
这是不同的情况,现在花括号只有一个元素,且正好是
other
变量的类型(即
std::vector<double>
),根据
列表初始化
重载解析规则,更多细节请参考
重载解析的特殊规则,以下发生了以下情况:
重载解析未选择
std::initializer_list<T>
构造函数,而是选择
复制构造函数,这是
完全匹配等级的情况,请阅读上面提供的链接了解详细信息。现在这个案例将有助于理解幕后发生了什么。
让我们看一下以下代码:
std::vector<double> other = {{0, 1}}
根据先前的讨论,编译器应该首先创建一个临时的
std::vector<double>
,然后再使用
移动初始化变量
other
,但幸运的是编译器并没有这样做,而是进行了优化,
因此,编译器不是创建一个临时的
std::vector<double>
,然后再
移动初始化变量
other
,而是优化掉了临时对象,并通过调用
std::initializer_list
的
构造函数直接使用
{0, 1}
作为构造参数来直接初始化
other
变量。
最后是最后一种情况,
std::vector<std::vector<double>> nestedVec = {{0, 1}}
编译器将首先创建一个临时的
std::vector<double>
,这意味着上述表达式在逻辑上变成了
std::vector<std::vector<double>> nestedVec = {std::vector<double>{0, 1}};
,然后重载决议将选择
std::initializer_list
构造函数。
所以学习的内容是,表达式
{{1, 2, 3}}
能够初始化
std::vector<double>
以及
std::vector<std::vector<double>>
,并在
bar()
函数调用中创建歧义,这也意味着应该谨慎使用带有单个元素的
std::initializer_list
。
为了消除歧义,可以采取以下措施:
更改函数调用,
bar({1, 2, 3});
bar({{1, 2, 3}, {}}); //put an empty element.
或者按照问题描述使用更多的花括号对
bar({{{1, 2, 3}}});
或者首先创建一个变量,并将此变量作为函数参数传递,而不是将std::initializer_list
作为函数参数传递。
以下代码将演示我所解释的内容,
using std::cout;
template <class T>
class Container{
public:
Container(){
cout<< "Default contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(const Container& ){
cout<< "Copy contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(Container&& ){
cout<< "Move contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
Container(const std::initializer_list<T>& ){
cout<< "std::initializer_list contructor.\n"<< __PRETTY_FUNCTION__<< '\n';
}
};
int main(int , char *[]){
std::cout<<"1 --- ";
Container<double> dObj; //1
cout<< '\n';
std::cout<<"2 --- ";
[[maybe_unused]] Container<double> cObj = {dObj}; //2
cout<< '\n';
std::cout<<"3 --- ";
[[maybe_unused]] Container<double> lObj = {{0, 1}}; //3
cout<< '\n';
std::cout<<"4 --- ";
[[maybe_unused]] Container<Container<double>> nObj = {{0, 1}}; //4
cout<< '\n';
}
输出:
1 --- Default contructor.
Container<T>::Container() [with T = double]
2 --- Copy contructor.
Container<T>::Container(const Container<T>&) [with T = double]
3 --- std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = double]
4 --- std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = double]
std::initializer_list contructor.
Container<T>::Container(const std::initializer_list<_Tp>&) [with T = Container<double>]