std :: tuple_cat,但仅包含唯一元素

时间:2019-06-14 05:12:24

标签: c++ templates tuples c++17 template-meta-programming

我需要一个非常类似于constexpr的{​​{1}}函数,但是除了将所有元素合并为一个元组之外,我并不需要将所有元素合并到一个元组中如果尚未添加该类型。

将谓词传递到std::tuple_cat会很好,但是不存在这样的API(令我很难过的是)。我已经看到了几种我无法完全理解的使用类型特征来查找合并类型的方法,但是没有任何形式的std::tuple_cat函数形式。我不确定如何将所有内容放在一起,尽管我确定可以做到。

类似这样的东西:

constexpr

1 个答案:

答案 0 :(得分:11)

可能的解决方案:

template<std::size_t i, class Tuple, std::size_t... is>
constexpr auto element_as_tuple(const Tuple& tuple, std::index_sequence<is...>)
{
    if constexpr (!(std::is_same_v<std::tuple_element_t<i, Tuple>, 
                  std::tuple_element_t<is, Tuple>> || ...))
        return std::make_tuple(std::get<i>(tuple));
    else
        return std::make_tuple();
}

template<class Tuple, std::size_t... is>
constexpr auto make_tuple_unique(const Tuple& tuple, std::index_sequence<is...>)
{
    return std::tuple_cat(element_as_tuple<is>(tuple, 
                          std::make_index_sequence<is>{})...);
}

template<class... Tuples>
constexpr auto make_tuple_unique(const Tuples&... tuples)
{
    auto all = std::tuple_cat(tuples...);
    constexpr auto size = std::tuple_size_v<decltype(all)>;
    return make_tuple_unique(all, std::make_index_sequence<size>{});
}

constexpr std::tuple<int, short, char> first(1, 2, 3);
constexpr std::tuple<short, float> second(4, 5);
constexpr std::tuple<int, double, short> third(6, 7, 8);
constexpr auto t = make_tuple_unique(first, second, third);
static_assert(std::get<0>(t) == 1);
static_assert(std::get<1>(t) == 2);
static_assert(std::get<2>(t) == 3);
static_assert(std::get<3>(t) == 5);
static_assert(std::get<4>(t) == 7);

通用化也适用于仅移动类型:

template<std::size_t i, class Tuple, std::size_t... is>
constexpr auto element_as_tuple(Tuple&& tuple, std::index_sequence<is...>)
{
    using T = std::remove_reference_t<Tuple>;
    if constexpr (!(std::is_same_v<std::tuple_element_t<i, T>, 
                  std::tuple_element_t<is, T>> || ...))         
        // see below
        // return std::forward_as_tuple(std::get<i>(std::forward<Tuple>(tuple)));
        return std::tuple<std::tuple_element_t<i, T>>(
            std::get<i>(std::forward<Tuple>(tuple)));
    else
        return std::make_tuple();
}

template<class Tuple, std::size_t... is>
constexpr auto make_tuple_unique(Tuple&& tuple, std::index_sequence<is...>)
{
    return std::tuple_cat(element_as_tuple<is>(std::forward<Tuple>(tuple), 
        std::make_index_sequence<is>())...);
}

template<class... Tuples>
constexpr auto make_tuple_unique(Tuples&&... tuples)
{
    auto all = std::tuple_cat(std::forward<Tuples>(tuples)...);
    return make_tuple_unique(std::move(all),
        std::make_index_sequence<std::tuple_size_v<decltype(all)>>{});
}

添加/更正

  

我的初始测试表明它运行良好,但更深入的测试表明,使用std::forward_as_tuple会生成对临时变量(make_tuple_unique中的“所有”变量)的引用。我不得不将std::forward_as_tuple更改为std::make_tuple,所有内容都已修复。

是正确的:如果您将右值作为参数传递,例如

make_tuple_unique(std::tuple<int>(1))

返回类型为std::tuple<int&&>,您会得到一个悬挂的引用。但是用std::make_tuple代替std::forward_as_tuple

make_tuple_unique(std::tuple<int&>(i))

将具有类型std::tuple<int>,并且引用将丢失。使用std::make_tuple可以放宽左值,而使用std::forward_as_tuple则可以放宽普通值。要保留原始类型,我们应该

return std::tuple<std::tuple_element_t<i, T>>(
    std::get<i>(std::forward<Tuple>(tuple)));