如何过滤标准::integer_sequence

How to filter a std::integer_sequence

本文关键字:integer sequence 标准 何过滤 过滤      更新时间:2023-10-16

如果我理论上有一个整数序列,例如

std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>

如何使用一些编译时谓词过滤它以获得可能更小的std::integer_sequence<int, ...>

为了论证,假设我只想要偶数值, 这就引出了"如何使以下static_assert(或接近的东西)通过?

static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>,
decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>, 
"Integer sequences should be equal");



这个问题的灵感来自于思考我们如何完成删除两个位集之间的重复项(这个问题),假设我们可以将位集表示为仅包含 0 和 1 的integer_sequences。如果你也能以这种方式解决这个问题,那么奖励积分

筛选序列等效于将值序列转换为最多一个值的序列序列,然后将它们连接起来。也就是说,从<0,1,2,3>中过滤偶数值与将其转换为序列<<0>,<>,<2>,<>>并连接以产生<0,2>相同。

对于 C++17,这需要非常少的代码。我们将从我们自己的值和序列类型开始(您可以轻松地将std::integer_sequence转换为value_sequence):

template <auto >
struct value { };
template <auto... Vals>
struct value_sequence { };

我们使用自己的原因是我们可以向其添加运算符。像+

template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>,
value_sequence<Bs...> )
{
return {};
}

我们将使用它进行串联。接下来,我们添加一个函数,将单个值转换为零个或一个元素的序列:

template <auto Val, class F>
constexpr auto filter_single(value<Val>, F predicate) {
if constexpr (predicate(Val)) {
return value_sequence<Val>{};
}
else {
return value_sequence<>{};
}
}

最后,我们只需要我们的顶级filter将它们放在一起:

template <auto... Vals, class F>
constexpr auto filter(value_sequence<Vals...>, F predicate) {
return (filter_single(value<Vals>{}, predicate) + ...);
}

原始示例中的用法:

constexpr auto evens = filter(
value_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{},
[](int i) constexpr { return i%2 == 0; });

C++17 有多酷!

Edit 2

在对Barry的答案进行了一些反复之后,我想出了以下答案,它合并了概念并处理了一些空序列边缘情况(完整代码):

只有当函数是constexprlambda 时,我们才允许将谓词传递给函数,因为constexpr函数中只允许文字类型,而普通的自由浮动函数不是文字类型(尽管我想你可以将一个包装在你的 lambda 中)。

我们的泛型过滤器函数将接受一个序列和一个谓词,并返回一个新序列。我们将使用constexpr if来处理空序列情况(这也需要在谓词上使用maybe_unused属性,因为它未使用):

template<class INT, INT... b, class Predicate>
constexpr auto Filter(std::integer_sequence<INT, b...>, [[maybe_unused]] Predicate pred)
{
if constexpr (sizeof...(b) > 0) // non empty sequence
return concat_sequences(FilterSingle(std::integer_sequence<INT, b>{}, pred)...);
else // empty sequence case
return std::integer_sequence<INT>{};
}

Filter函数为所提供序列中的每个元素调用FilterSingle,并连接所有元素的结果:

template<class INT, INT a, class Predicate>
constexpr auto FilterSingle(std::integer_sequence<INT, a>, Predicate pred)
{
if constexpr (pred(a))
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}

因此,要连接序列,基本方法是:

template<typename INT, INT... s, INT... t>
constexpr std::integer_sequence<INT,s...,t...>
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>){
return {};
}

尽管由于模板扩展,我们很多时间都会有超过 2 个序列,因此我们需要一个递归情况:

template<typename INT, INT... s, INT... t, class... R>
constexpr auto
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>, R...){
return concat_sequences(std::integer_sequence<INT,s...,t...>{}, R{}...);
}

由于我们可能会尝试连接一个空序列,没有任何内容(如果没有元素通过过滤器,可能会发生),我们需要另一种基本情况:

template<typename INT>
constexpr std::integer_sequence<INT>
concat_sequences(std::integer_sequence<INT>){
return {};
}

现在,对于我们的谓词,我们将使用constexprlambda。请注意,我们不需要显式将其指定为constexpr,因为它已经满足自动成为constexpr的条件

auto is_even = [](int _in) {return _in % 2 == 0;};

所以我们的完整测试看起来像这样:

auto is_even = [](int _in) {return _in % 2 == 0;};
using expected_type = std::integer_sequence<int, 0, 2, 4, 6, 8>;
using test_type = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>;
constexpr auto result = Filter(test_type{}, is_even);
using result_type = std::decay_t<decltype(result)>;
static_assert(std::is_same_v<expected_type, result_type>, "Integer sequences should be equal");

以前的方法

我的方法是反复构造和连接子序列,其中基本情况(一个序列)将返回空序列或相同的序列(如果满足谓词)。

为了编写谓词,我将利用 C++17 的 constexpr if 来定义谓词。

谓语:

// base case; empty sequence
template<class INT>
constexpr auto FilterEvens(std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// base case; one element in the sequence
template<class INT, INT a>
constexpr auto FilterEvens(std::integer_sequence<INT, a>)
{
if constexpr (a % 2 == 0)
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b>
constexpr auto FilterEvens(std::integer_sequence<INT, a, b...>)
{
return concat_sequence(FilterEvens(std::integer_sequence<INT, a>{}), 
FilterEvens(std::integer_sequence<INT, b...>{}));
}

串联逻辑:

template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}

和测试:

int main()
{
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>, decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>, "Integer sequences should be equal");
}

现场演示

>编辑:我使用这种方法来解决此处删除匹配位的"奖金"问题:https://stackoverflow.com/a/41727221/27678

利用元组的替代解决方案:

template <auto Pred, class Type, Type... I>
struct filter_integer_sequence_impl
{
template <class... T>
static constexpr auto Unpack(std::tuple<T...>)
{
return std::integer_sequence<Type, T::value...>();
}
template <Type Val>
using Keep = std::tuple<std::integral_constant<Type, Val>>;
using Ignore = std::tuple<>;
using Tuple = decltype(std::tuple_cat(std::conditional_t<(*Pred)(I), Keep<I>, Ignore>()...));
using Result = decltype(Unpack(Tuple()));
};
template <auto Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...>)
{
return typename filter_integer_sequence_impl<Pred, Type, I...>::Result();
}
template <class Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...> sequence, Pred)
{
return filter_integer_sequence<(Pred*)nullptr>(sequence);
}

像这样使用:

constexpr auto predicate = [](int val) { return (val % 2) == 0; };
constexpr auto start = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>();
constexpr auto filtered = filter_integer_sequence(start, predicate);
constexpr auto expected = std::integer_sequence<int, 0, 2, 4, 6, 8>();
static_assert(std::is_same_v<decltype(filtered), decltype(expected)>);

filter_integer_sequence的第二次重载仅适用于 C++17,这不允许我们将 lambda 用于非类型模板参数。C++20 解除了该限制,因此在这种情况下,只需要第一次过载。请注意,在 C++17 中,处理常规函数指针时,仍然需要第一次重载。

与此处的其他解决方案一样,第二个重载仅适用于非捕获 lambda。这就是为什么我们可以安全地将(*(Pred*)nullptr)(I)评估为 constexpr 的原因,因为 lambda 主体实际上并不使用this指针。