为什么原始的卷曲构造函数{}不返回右值?

时间:2018-08-28 05:52:13

标签: c++ templates c++17 variadic-templates generic-lambda

让我们说您有一个带有std::tuple的可变参数类,可以使用args + 1个新arg进行构造。使用std::apply()和原始花括号构造函数构造时,该构造函数不会返回右值。这意味着该类不是可移动构造的。下面是一个例子来澄清。

#include <cstdio>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>

template <class... Args>
struct icecream {
    icecream() = default;

    template <class... MoreArgs>
    icecream(icecream<MoreArgs...>&& ice) {
        std::apply(
                [this](auto&&... ds) {
                    data = { std::move(ds)..., {} };
                },
                std::move(ice.data));
    }

    // This works :

    // template <class... MoreArgs>
    // icecream(icecream<MoreArgs...>&& ice) {
    //  std::apply(
    //          [this](auto&&... ds) {
    //              data = { std::move(ds)...,
    //                  std::move(std::vector<double>{}) };
    //          },
    //          std::move(ice.data));
    // }

    std::tuple<std::vector<Args>...> data{};
};

int main(int, char**) {
    icecream<int> miam;
    std::get<0>(miam.data).push_back(1);
    std::get<0>(miam.data).push_back(2);

    icecream<int, double> cherry_garcia{ std::move(miam) };

    printf("miam : \n");
    for (const auto& x : std::get<0>(miam.data)) {
        printf("%d\n", x);
    }

    printf("\ncherry_garcia : \n");
    for (const auto& x : std::get<0>(cherry_garcia.data)) {
        printf("%d\n", x);
    }

    return 0;
}

输出为:

miam : 
1
2

cherry_garcia : 
1
2

该示例有些笨拙,但说明了这一点。在第一个move构造函数中,使用{}和元组副本构造。如果您用硬编码的std::move()取消注释第二个构造函数,那么它将起作用。

我测试VS最新,clang最新和gcc最新。全部具有相同的结果。 (wandbox:https://wandbox.org/permlink/IQqqlLcmeyOzsJHC

问题是,为什么不返回右值?我显然缺少卷曲的构造函数。这可能与可变参数无关,但我想我也应该展示真实的情况。

1 个答案:

答案 0 :(得分:7)

  

为什么原始的卷曲构造函数{}不返回右值?

问题是另一个。

问题是

data = { std::move(ds)..., {} };

调用“直接构造函数”(this page中的构造函数(2)),

constexpr tuple( const Types&... args );       (2)

不是“转换构造函数”(构造函数(3))

template< class... UTypes >
constexpr tuple( UTypes&&... args );           (3)

您期望的。

问题在于,对于编译器而言,“ {}”不足以推导类型(构造函数(3)中UTypes...列表的最后一个类型),因此构造函数(3)被排除,编译器选择构造函数(2)。

Whit构造函数(2),可以使用“ {}”构造列表中Types...的最后一种对象,因为Types...是已知的并且不可以推论。

但是构造函数(2)是复制构造函数(从元组的Types...角度来看),而不是正向构造函数作为构造函数(3),因此第一个向量是复制的,而不是移动的。

打电话时不一样

data = { std::move(ds)..., std::move(std::vector<double>{}) };

还是

data = { std::move(ds)..., std::vector<double>{} };

因为最后一个参数可以明确推导为std::vector<double>{} &&,所以编译器调用“转换构造函数”(构造函数(3))并移动第一个向量的内容。

非主题:仅当std::vector<double>{}double中的最后一个类型时才使用Args...,我建议使用{{1} }。

此外,我建议SFINAE仅在std::tuple_element时启用您的构造函数。

也许也sizeof...(MoreArgs)+1u == sizeof...(Args)(启用完美转发),而不是std::forward()在lambda中。

所以我建议以下构造函数

std::move()