在本视频的最后(从 15:57 开始),有关于如何在 C++17 中使用几乎统一的初始化的建议: 视频在这里
要点是这样的:始终使用直接初始化auto a{...};
和MyType a{...};
不要对类型使用复制初始化= {...}
。
#include <iostream>
struct MyType {
explicit MyType(std::initializer_list<int>) {
std::cout << "Called std::initializer_list<int>" << std::endl;
}
explicit MyType(int) {
std::cout << "Called int." << std::endl;
}
MyType(int, int, int) {
std::cout << "Called int, int, int" << std::endl;
}
};
int main() {
MyType calls_init_list{10}; //Calls initializer_list<int>
MyType calls_init_list_2{10, 20}; //Calls initializer_list<int>
MyType calls_init_list_3{10, 20, 30}; //Calls initializer_list<int>
MyType compile_error = {10, 20, 30}; //Compile error
}
如果我从第一个构造函数中删除显式,它也将调用第 4 次调用initializer_list<int>
- 我需要进行哪些更改才能按照视频中的规则呼叫
(int)
和(int, int, int)
? - 是否可以在初始值设定项列表构造函数存在的情况下调用其他构造函数?
- 有什么设计建议可以避免放弃视频中建议的一般规则吗?终于有有意义的东西会很好,C++初始化可能是其中最可怕的部分。
我需要进行哪些更改才能按照视频中的规则调用 (int) 和 (int, int, int)?
删除initializer_list<int>
构造函数。这是使其发挥作用的唯一途径。
是否可以在初始值设定项列表构造函数存在的情况下调用其他构造函数?
是的,只要大括号初始化列表中的类型无法与任何initializer_list<T>
构造函数中的类型匹配。他们总是有首要地位。
因此,为什么它被嘲笑地称为">几乎一致的初始化"。
典型的解决方案是向非initializer_list
构造函数添加一些标记类型:
struct tag_t {};
constexpr inline tag_t tag;
struct MyType {
explicit MyType(std::initializer_list<int>) {
std::cout << "Called std::initializer_list<int>" << std::endl;
}
MyType(tag_t, int) {
std::cout << "Called int." << std::endl;
}
MyType(tag_t, int, int, int) {
std::cout << "Called int, int, int" << std::endl;
}
};
int main() {
MyType three_int = {tag, 10, 20, 30}; //Calls 3-`int` constructor
}
有什么设计建议可以避免放弃视频中建议的一般规则吗?
好吧,考虑到"一般规则"不是一个好规则(他的幻灯片包含典型的反例:尝试用大括号调用vector<int>
的大小+值版本),最好放弃它。关于auto a{2};
翻译成什么的小争论与字面上无法调用某些构造函数无关紧要。
在您的情况下,要调用MyType(int, int, int)
或explicit MyType(int)
,您必须使用()
语法而不是{}
。
基本上,我认为总是使用{}
语法不是一个好主意。例如,从 C++17 开始,标准库中的所有 emplace 方法都在内部使用()
而不是{}
。例如,代码
std::vector<std::vector<int>> vv;
vv.emplace_back(2, 1);
<1, 1>
不<2, 1>
。这也是标准容器不支持骨料类型构造的原因。
在我看来,您可以坚持的真正统一初始化是尽可能执行()
初始化,并以其他方式回退到{}
(例如,对于聚合类型)。另请参阅此。可能的实现:
template <typename...>
struct paren_initable: std::false_type {};
template <typename T, typename... Us>
struct paren_initable<decltype((void)T(std::declval<Us>()...)), T, Us...>
: std::true_type {};
template <typename T, typename... Us>
inline constexpr bool paren_initable_v = paren_initable<void, T, Us...>::value;
template <typename T, typename... Us>
T emplace(Us&&... us) {
if constexpr (paren_initable_v<T, Us...>) {
return T(std::forward<Us>(us)...);
}
else {
return T{std::forward<Us>(us)...};
}
}