2

假设您有一个带有 a 的可变参数类std::tuple,可以使用 args + 1 new 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

这个例子有点愚蠢,但说明了这一点。在第一个移动构造函数中,{}使用了元组复制构造。如果您使用硬编码取消注释第二个构造函数std::move(),则它可以工作。

我在最新的 VS、最新的 clang 和最新的 gcc 上进行测试。都有相同的结果。(魔杖盒: https ://wandbox.org/permlink/IQqqlLcmeyOzsJHC )

所以问题是,为什么不返回一个右值呢?我显然缺少 curly 构造函数的一些东西。这可能与可变参数无关,但我想我不妨展示一下真实场景。

4

1 回答 1

7

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

问题是另一个。

问题是

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

调用“直接构造函数”(本页中的构造函数(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...std::tuple_element

此外,我建议 SFINAE 仅在sizeof...(MoreArgs)+1u == sizeof...(Args).

也许也std::forward()(启用完美转发)而不是std::move()在 lambda 中。

所以我建议以下构造函数

template <typename ... MoreArgs,
   std::enable_if_t<sizeof...(MoreArgs)+1u == sizeof...(Args)> * = nullptr>
icecream(icecream<MoreArgs...>&& ice) {
    std::apply(
            [this](auto && ... ds) {
                data = { std::forward<decltype(ds)>(ds)..., 
                         std::tuple_element_t<sizeof...(Args)-1u,
                                              decltype(data)>{} };
            },
            std::move(ice.data));
}
于 2018-08-28T10:31:15.407 回答