If I theoretically have a sequence of integers like
std::integer_sequence
How can I filter it with
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");
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
I used this approach to solve the "Bonus" question for removing matched bits here: https://stackoverflow.com/a/41727221/27678