为什么make_tuple的实现不会通过大括号初始化返回?

时间:2018-01-31 01:25:04

标签: c++ c++11 stdtuple c++17 list-initialization

要理解这个问题,请先阅读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之前,没有保证复制省略甚至在概念上删除了临时版本。但即使使用C ++ 17,我也不一定希望花括号在这个例子中消失。

为什么在任何C ++ 11/14实现中都没有花括号?换句话说,为什么不呢

 template <class... Types>
    std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
    {
        return {std::forward<Types>(args)...};
    }

1 个答案:

答案 0 :(得分:5)

您所谈论的优势根本不适用于此案例。是的,你可以用这种方式构建一个不可移动的类型:

struct Nonmovable {
    Nonmovable(int );
    Nonmovable(Nonmovable&& ) = delete;
};

Nonmovable foo() { return {42}; } // ok

但是在make_tuple的上下文中,所有元素都可以移动或复制,因为你将要移动或复制它们到实际的{{ 1}}你正在构建:

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)...); } 必须是可移动的。

在这里使用tuple至少有一个奇怪的缺点,即在一个类型有一个{...}移动或复制构造函数的情况下,返回一个 braced-init-列表不起作用。

更重要的是,作为T.C.指出,在Improving pair and tuple被采用之前,std::tuple的构造函数总是 explicit

explicit

这使得实现返回 braced-init-list 成为不可能。所以每个库在C ++ 17之前都已经有// 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 ); 实现了,它不能使用 braced-init-list ,并且没有任何好处来改变它 - 所以我们今天就在这里。