如何过滤std :: integer_sequence

时间:2017-01-18 15:51:06

标签: c++ c++17

如果我理论上有一个像

这样的整数序列
std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>

如何使用某些编译时谓词对其进行过滤以获得可能较小的std::integer_sequence<int, ...>

为了论证,让我们说我只想要偶数值, 这导致了问题&#34;如何使以下static_assert(或接近的东西)通过?&#34;

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");



这个问题的灵感来自于考虑如何在两个位集(this question)之间删除重复项,假设我们可以将位集表示为仅包含0和1的integer_sequences。如果可以的话,可以获得奖励积分以这种方式解决这个问题

3 个答案:

答案 0 :(得分:10)

过滤序列相当于将一系列值转换为最多一个值的序列序列,然后将它们连接起来。也就是说,从<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有多酷!

答案 1 :(得分:5)

编辑2

经过Barry的回答,我已经提出了以下答案,它合并了概念并处理了一些空序列边缘情况(Full code):

我们被允许仅在谓词是constexpr lambda的情况下将谓词传递给函数,因为constexpr functions中只允许使用文字类型,并且正常的自由浮动函数不是字面值类型(虽然我想你可以在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 {};
}

现在,对于我们的谓词,我们将使用constexpr lambda。请注意,我们无需明确指定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");
}

Live Demo

编辑:

我用这种方法来解决&#34;奖金&#34;有关删除匹配位的问题:https://stackoverflow.com/a/41727221/27678

答案 2 :(得分:1)

利用元组的另一种解决方案:

#include <stdio.h>

int main(void)
{
    const char* ptr = "hello";
    printf("The value of ptr is %p\n", ptr);
    ptr = "world";
    printf("The value of ptr is %p\n", ptr);
}

像这样使用:

The address of ptr is 0000000000404000
The address of ptr is 0000000000404020

使用lambda使其工作需要c ++ 20,因此您可以通过template参数传递lambda。