How to filter a std::integer_sequence

前端 未结 3 1721
一个人的身影
一个人的身影 2021-01-06 01:49

If I theoretically have a sequence of integers like

std::integer_sequence

How can I filter it with

3条回答
  •  夕颜
    夕颜 (楼主)
    2021-01-06 02:17

    Edit 2

    After some back and forth on Barry's answer, I've come up with the following answer that merges the concepts and handles some empty-sequence edge cases (Full code):

    We are allowed to pass a predicate to a function only if it is a constexpr lambda, as only literal types are allowed in constexpr functions, and normal free-floating functions aren't literal types (although I suppose you could wrap one within your lambda).

    Our generic filter function will accept a sequence and a predicate, and return a new sequence. We will use constexpr if to handle empty sequence cases (which also requires the maybe_unused attribute on the predicate, because it's unused) :

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

    The Filter function calls FilterSingle for each element in the provided sequence, and concatenates the result of all of them:

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

    To concatenate sequences, the basic approach is thus:

    template
    constexpr std::integer_sequence
    concat_sequences(std::integer_sequence, std::integer_sequence){
        return {};
    }
    

    Although because of template expansion we'll have more than 2 sequences a lot of time, so we need a recursive case:

    template
    constexpr auto
    concat_sequences(std::integer_sequence, std::integer_sequence, R...){
        return concat_sequences(std::integer_sequence{}, R{}...);
    }
    

    And since we may try to concatenate an empty sequence with nothing (can happen if no elements pass the filter), we need another base case:

    template
    constexpr std::integer_sequence
    concat_sequences(std::integer_sequence){
        return {};
    }
    

    Now, for our predicate we will use a constexpr lambda. Note that we do not need to specify it as constexpr explicitly because it already satisfies the criteria to automatically become constexpr

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

    So our full test looks like this:

    auto is_even = [](int _in) {return _in % 2 == 0;};
    using expected_type = std::integer_sequence;
    using test_type = std::integer_sequence;
    constexpr auto result = Filter(test_type{}, is_even);
    using result_type = std::decay_t;
    static_assert(std::is_same_v, "Integer sequences should be equal");
    

    Previous approach

    My approach is repeatedly construct and concatenate sub-sequences, where the base case (sequence of one) will either return an empty sequence or the same sequence if the predicate is satisfied.

    For writing the predicate, I'll take advantage of C++17's constexpr if for defining a predicate.

    Predicate:

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

    Concatenation logic:

    template 
    constexpr auto
    concat_sequence(std::integer_sequence,std::integer_sequence){
       return std::integer_sequence{};
    }
    

    And the test:

    int main()
    {
       static_assert(std::is_same_v, decltype(FilterEvens(std::integer_sequence{}))>, "Integer sequences should be equal");
    }
    

    Live Demo


    Edit:

    I used this approach to solve the "Bonus" question for removing matched bits here: https://stackoverflow.com/a/41727221/27678

提交回复
热议问题