假设我有一个存储类型为T的对象的模板.我想传递构造函数参数以初始化数据成员.我应该使用统一初始化还是使用非花括号进行直接初始化?:
template<typename T> struct X { template<typename... Args> X(Args&&... args) : t(std::forward<Args>(args)...) // ? /* or */ : t{std::forward<Args>(args)...} // ? private: T t; };
如果我想存储的对象是std :: vector并且我选择了花括号样式(uniform-initialization),那么我传递的参数将被转发到vector :: vector(std :: initializer_list< T>)构造函数,这可能是也可能不是我想要的.
另一方面,如果我使用非大括号样式,我将失去通过其std :: initializer_list构造函数向向量添加元素的能力.
当我不知道我存储的对象和将传入的参数时,我应该使用什么形式的初始化?
解决方法
为了清楚起见,对于具有多个构造函数的类型产生了歧义,包括一个使用std :: initializer_list,另一个构造函数的参数(当用大括号初始化时)可能被编译器解释为std :: initializer_list.例如,使用std :: vector< int>就是这种情况. :
template<typename T> struct X1 { template<typename... Args> X1(Args&&... args) : t(std::forward<Args>(args)...) {} T t; }; template<typename T> struct X2 { template<typename... Args> X2(Args&&... args) : t{std::forward<Args>(args)...} {} T t; }; int main() { auto x1 = X1<std::vector<int>> { 42,2 }; auto x2 = X2<std::vector<int>> { 42,2 }; std::cout << "size of X1.t : " << x1.t.size() << "\nsize of X2.t : " << x2.t.size(); }
(注意,唯一的区别是X2成员初始化列表中的大括号而不是X1成员初始化列表中的括号)
Output :
size of X1.t : 42
size of X2.t : 2
标准库作者在编写实用程序模板时遇到了这个问题,例如std :: make_unique,std :: make_shared或std :: optional<> (应该完全转发任何类型):首选哪种初始化形式?这取决于客户端代码.
没有好的答案,他们通常使用括号(理想情况下记录选择,因此调用者知道会发生什么).习惯性的现代c 11更喜欢在任何地方进行支撑初始化(它避免缩小转换,避免c最令人烦恼的解析等).
消除歧义的潜在解决方法是使用命名标记,在Andrzej’s C++ blog这篇伟大的文章中广泛讨论:
namespace std{ constexpr struct with_size_t{} with_size{}; constexpr struct with_value_t{} with_value{}; constexpr struct with_capacity_t{} with_capacity{}; } // These contructors do not exist. std::vector<int> v1(std::with_size,10,std::with_value,6); std::vector<int> v2{std::with_size,6};
这是详细的,并且只有在您可以修改歧义类型时才适用(例如,暴露构造函数的类型采用std :: initializer_list和其参数列表可能转换为std :: initializer列表的其他构造函数)