为什么 make_tuple 的实现不是 return 通过大括号初始化?
Why does implementation of make_tuple not return via brace initialisation?
要理解问题,请先阅读 this answer。
我检查了不同的历史 make_tuple
实现(包括 2012 年的 clang 版本)。在 C++17 之前,我希望它们 return {list of values ... }
但它们都在返回元组之前构造它。它们都与非常简化的当前 cppreference 示例一致:
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
没有错,但是返回大括号初始化的意义在于直接构造返回对象。在 C++17 之前,没有 guaranteed copy elision 甚至在概念上删除临时对象。但即使使用 C++17,我也不一定希望花括号在此示例中消失。
为什么在任何 C++11/14 实现中都没有花括号?换句话说,为什么不
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
您所说的优势在这种情况下根本不适用。是的,你可以这样构造一个不可移动的类型:
struct Nonmovable {
Nonmovable(int );
Nonmovable(Nonmovable&& ) = delete;
};
Nonmovable foo() { return {42}; } // ok
但是在 make_tuple
的上下文中,所有元素 都必须 是可移动或可复制的,因为您要将它们移动或复制到您正在构建的实际 tuple
:
std::tuple<Nonmovable> make_tuple(Nonmovable&& m) {
return {std::move(m)}; // still an error
}
所以没有真正的优势:
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
超过
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
在这个意义上。
是的,按照标准的语言,其中一个意味着直接构建到 return 对象中,另一个涉及临时对象。但实际上,每个编译器都会优化它。我们已经知道不可能的一种情况不适用 - 我们的 tuple
必须是可移动的。
这里使用 {...}
至少有一个奇怪的缺点,那就是如果一个类型有一个 explicit
移动或复制构造函数,return braced-init-list 不起作用。
更重要的是,T.C。指出,直到 Improving pair
and tuple
was adopted, the constructor for std::tuple
一直是 explicit
.
// in C++11
explicit tuple( const Types&... args );
// in C++14
explicit constexpr tuple( const Types&... args );
// in C++17, onwards
/*EXPLICIT*/ constexpr tuple( const Types&... args );
这使得实现 returning braced-init-list 变得不可能。因此,每个库在 C++17 之前都已经有一个 make_tuple()
实现,它不能使用 braced-init-list,并且更改它没有任何好处 -所以我们就是今天的样子。
要理解问题,请先阅读 this answer。
我检查了不同的历史 make_tuple
实现(包括 2012 年的 clang 版本)。在 C++17 之前,我希望它们 return {list of values ... }
但它们都在返回元组之前构造它。它们都与非常简化的当前 cppreference 示例一致:
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
没有错,但是返回大括号初始化的意义在于直接构造返回对象。在 C++17 之前,没有 guaranteed copy elision 甚至在概念上删除临时对象。但即使使用 C++17,我也不一定希望花括号在此示例中消失。
为什么在任何 C++11/14 实现中都没有花括号?换句话说,为什么不
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
您所说的优势在这种情况下根本不适用。是的,你可以这样构造一个不可移动的类型:
struct Nonmovable {
Nonmovable(int );
Nonmovable(Nonmovable&& ) = delete;
};
Nonmovable foo() { return {42}; } // ok
但是在 make_tuple
的上下文中,所有元素 都必须 是可移动或可复制的,因为您要将它们移动或复制到您正在构建的实际 tuple
:
std::tuple<Nonmovable> make_tuple(Nonmovable&& m) {
return {std::move(m)}; // still an error
}
所以没有真正的优势:
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
超过
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
在这个意义上。
是的,按照标准的语言,其中一个意味着直接构建到 return 对象中,另一个涉及临时对象。但实际上,每个编译器都会优化它。我们已经知道不可能的一种情况不适用 - 我们的 tuple
必须是可移动的。
这里使用 {...}
至少有一个奇怪的缺点,那就是如果一个类型有一个 explicit
移动或复制构造函数,return braced-init-list 不起作用。
更重要的是,T.C。指出,直到 Improving pair
and tuple
was adopted, the constructor for std::tuple
一直是 explicit
.
// in C++11
explicit tuple( const Types&... args );
// in C++14
explicit constexpr tuple( const Types&... args );
// in C++17, onwards
/*EXPLICIT*/ constexpr tuple( const Types&... args );
这使得实现 returning braced-init-list 变得不可能。因此,每个库在 C++17 之前都已经有一个 make_tuple()
实现,它不能使用 braced-init-list,并且更改它没有任何好处 -所以我们就是今天的样子。