为什么 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,并且更改它没有任何好处 -所以我们就是今天的样子。