在C++17中过滤类型元组

7

std::tuple a{1,3,4,5} -> 将它变成大于3的数字


std::tuple b{4,5}    

或者

std::tuple a{
    std::integral_constant<int,1> {},
    std::integral_constant<int,3> {},
    std::integral_constant<int,4> {},
    std::integral_constant<int,5> {} 
}

to

std::tuple a{
    std::integral_constant<int,4>{},
    std::integral_constant<int,5>{}
};

如何在编译时进行转换?我可以使用integer_sequence来实现,但那很麻烦。在C++17中是否有更简单的方法,比如使用折叠表达式或std::apply
此外,在过滤之后,还需要获得一个唯一条目的元组。但我的假设是,如果过滤可以完成,则查找唯一条目将是微不足道的。
编辑以使其更清晰: std::tuple<int_c<1>, int_c<3>,int_c<4>,int_c<5>> to std::tuple<int_c<4>,int_c<5> <-- 如果没有额外的声明函数,能否以简洁的C++17方式实现这样的转换!
编辑: 我正在尝试,也许像这样会起作用: 使用template... C作为整数常量列表:
constexpr auto result = std::tuple_cat(std::conditional_t<(C::value > 3), std::tuple<C>, std::tuple<>>{}...);

1
编译时必须知道结果类型,根据值无法实现该操作。 - Jarod42
如果结果是 std::vector<int>,那么这是可能的。 - Maxim Egorushkin
但您可以将 template <std::size_t N>using int_c = std::integral_constant<int, N>; std::tuple<int_c<1>, int_c<3>,int_c<4>,int_c<5>> 转换为 std::tuple<int_c<4>,int_c<5>> - Jarod42
@Jarod42 如果这些值实际上是包装类型,那么不可能吗? - themagicalyang
std::tuple<int_c<1>, int_c<3>,int_c<4>,int_c<5>> 转换为 std::tuple<int_c<4>,int_c<5>> 是可行的! - themagicalyang
3个回答

11

使用C++17进行元组拼接的方法是:

constexpr auto result = std::apply([](auto...ts) {
    return std::tuple_cat(std::conditional_t<(decltype(ts)::value > 3),
                          std::tuple<decltype(ts)>,
                          std::tuple<>>{}...);
}, tup);

这为简洁解决方案提供了正确的方向,但是这里的代码无法编译。我用auto... ts替换了第一个Ts...,并用decltype(ts)替换了其他Ts:然后它完美地工作了。 - Yongwei Wu
如果输入是 std::tuple a{1,3,4,5},这种方法依赖于将值编码为类型 integral_constant,该类型可以用作模板参数。对于 int 来说无论如何都没有关系,但是当元组元素是 constexpr 结构体时,使用 integral_constant 就显得冗长了。 - Joey.Z
@zoujyjs:返回类型不能依赖于运行时值。你可以使用类似template<auto... Values> using tupleValue = std::tuple<std::integral_constant<decltype(Values), Values>...>;的方式来减少冗余(如tupleValue<1, 3, 4, 5, '*', 1ULL>)。 - Jarod42

3
一种可行的解决方案是创建一个特性(trait)。对于期望的元素T,该特性将输出std::tuple<T>,对于不期望的元素,输出std::tuple<>。然后使用std::tuple_cat将这些元组合并成单一类型。例如:
#include <tuple>
#include <type_traits>
#include <utility>

template <typename Pred, typename Tuple> struct filter;

template <typename t_Predicate, typename ...Ts> 
struct filter<t_Predicate, std::tuple<Ts...>>
{
    // If this element has to be kept, returns `std::tuple<Ts>`
    // Otherwise returns `std::tuple<>`
    template<class E>
    using t_filter_impl = std::conditional_t<
        t_Predicate<E>::value,
        std::tuple<E>, std::tuple<>>;

    // Determines the type that would be returned by `std::tuple_cat`
    //  if it were called with instances of the types reported by 
    //  t_filter_impl for each element
    using type = decltype(std::tuple_cat(std::declval<t_filter_impl<Ts>>()...));
};

在这里,t_Predicate<T>指任何谓词类型,该类型具有一个成员bool value;,该成员确定是否T是一个可取的类型。例如,要将此解决方案应用于原始问题,请首先编写专门针对std::integral_constant进行的谓词类型:

// Non integral_constant are not kept
template<class T>
struct four_or_more : std::integral_constant<bool, false> {};

// integral_const types are kept if their value is >=4
template<class T, T V>
struct four_or_more<std::integral_constant<T, V>> :
    std::integral_constant<bool, V >= 4> {};

以下是一个示例:

#include <iostream>

int main()
{
    auto a = std::make_tuple(
        std::integral_constant<int,1> {},
        std::integral_constant<int,3> {},
        std::integral_constant<int,4> {},
        std::integral_constant<int,5> {}
    );

    using b_type = filter<four_or_more, decltype(a)>::type;

    std::cout << "size : " << std::tuple_size<b_type>() << std::endl;
    std::cout << std::tuple_element_t<0, b_type>::value << std::endl;
    std::cout << std::tuple_element_t<1, b_type>::value << std::endl;
}

如果您使用>= 4来避免模板结尾的问题,注意您可以使用括号<bool,(V>3)> - Jarod42

1
你可以使用C++17的新STL工具来实现这一点。大致如下所示:
template<typename T>
auto filter(T tup) {
    return std::apply([&](auto first, auto... rest) {
        auto filtered_rest = [&]{
            if constexpr (sizeof...(rest)) {
                return filter(std::tuple{rest...});
            } else {
                return std::tuple{};
            }
        }();

        if constexpr (first > 3) {
            return std::tuple_cat(std::tuple{first}, filtered_rest);
        } else {
            return filtered_rest;
        }
    }, tup);
}

当然,还有许多其他方法可以完成此操作。在这种情况下,我使用了std :: apply和递归。我从一个空元组开始,然后逐个添加元素。
实时示例:https://godbolt.org/z/qo63r4

无法编译,即使两个lambda的返回类型都不同。 - Joey.Z
@zoujyjs 除了缺少捕获之外,它可以编译。请再次检查您的规则。 - Guillaume Racicot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接