C++17 几乎一致的初始化
C++17 almost uniform initialization
在本视频的结尾(从 15:57 开始)有关于如何在 C++17 中使用几乎一致的初始化的建议:video here
要点是这样的:始终使用直接初始化 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
}
如果我从第一个构造函数中删除 explicit ,它将调用第 4 次调用 initializer_list<int>
- 我需要做哪些更改才能按照视频中的规则调用
(int)
和 (int, int, int)
?
- 在存在初始化列表构造函数的情况下,甚至可以调用其他构造函数吗?
- 有什么设计建议可以避免放弃视频中建议的一般规则吗?终于有一些有意义的东西会很好,C++ 初始化可能是其中最糟糕的部分。
在您的情况下,要调用 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>
。这也是标准容器不支持聚合类型的 emplace 构造的原因。
在我看来,您可以坚持的真正统一初始化是在可能的情况下执行 ()
初始化,否则回退到 {}
的初始化(例如,对于聚合类型)。另见 this。可能的实现:
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)...};
}
}
What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?
删除 initializer_list<int>
构造函数。这是让它发挥作用的唯一方法。
Is it even possible to call the other constructors in the presence of the initializer list constructor?
是的,只要花括号初始化列表中的类型不能匹配任何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
}
any design recommendations to avoid abandoning the general rule adviced in the video?
嗯,考虑到 "general rule" 不是一个好的规则(他的幻灯片包含典型的反例:尝试用大括号调用 vector<int>
的大小+值版本),这样更好放弃它。关于 auto a{2};
翻译成什么的小问题与字面上无法调用某些构造函数无关。
在本视频的结尾(从 15:57 开始)有关于如何在 C++17 中使用几乎一致的初始化的建议:video here
要点是这样的:始终使用直接初始化 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
}
如果我从第一个构造函数中删除 explicit ,它将调用第 4 次调用 initializer_list<int>
- 我需要做哪些更改才能按照视频中的规则调用
(int)
和(int, int, int)
? - 在存在初始化列表构造函数的情况下,甚至可以调用其他构造函数吗?
- 有什么设计建议可以避免放弃视频中建议的一般规则吗?终于有一些有意义的东西会很好,C++ 初始化可能是其中最糟糕的部分。
在您的情况下,要调用 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>
。这也是标准容器不支持聚合类型的 emplace 构造的原因。
在我看来,您可以坚持的真正统一初始化是在可能的情况下执行 ()
初始化,否则回退到 {}
的初始化(例如,对于聚合类型)。另见 this。可能的实现:
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)...};
}
}
What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?
删除 initializer_list<int>
构造函数。这是让它发挥作用的唯一方法。
Is it even possible to call the other constructors in the presence of the initializer list constructor?
是的,只要花括号初始化列表中的类型不能匹配任何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
}
any design recommendations to avoid abandoning the general rule adviced in the video?
嗯,考虑到 "general rule" 不是一个好的规则(他的幻灯片包含典型的反例:尝试用大括号调用 vector<int>
的大小+值版本),这样更好放弃它。关于 auto a{2};
翻译成什么的小问题与字面上无法调用某些构造函数无关。