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<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>{};
}
The Filter
function calls FilterSingle
for each element in the provided sequence, and concatenates the result of all of them:
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>{};
}
To concatenate sequences, the basic approach is thus:
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 {};
}
Although because of template expansion we'll have more than 2 sequences a lot of time, so we need a recursive case:
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{}...);
}
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<typename INT>
constexpr std::integer_sequence<INT>
concat_sequences(std::integer_sequence<INT>){
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<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");
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<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...>{}));
}
Concatenation logic:
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...>{};
}
And the test:
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
Edit:
I used this approach to solve the "Bonus" question for removing matched bits here: https://stackoverflow.com/a/41727221/27678