如何使用 `Unary Predicate` lambda 过滤 constexpr `std::array`?
How to filter a constexpr `std::array` with a `UnaryPredicate` lambda?
给定一个 constexpr std::array
和一个 UnaryPredicate pred
,我如何将数组过滤为 return 一个新的 constexpr std::array
,其所有元素都满足 pred(e)
?
struct foo {
int f1;
int f2;
};
static constexpr std::array<foo, 5> arr{ foo{1,1}, {2,2}, {3,3}, {4,4}, {5,5} };
int main() {
// will result in { foo{1,1}, {2,2}, {4,4}, {5,5} }
constexpr auto filterd = filter_array(arr, [](const foo& f) {return f.f1 != 3;})
return 0;
}
肯定是,依赖类型,依赖于值的类型,在present-day C++中是不可能的。您不能仅通过函数参数来决定结果类型 std::array<T, M>
的大小 M
,这意味着 M
必须是模板参数。
因此您必须通过 constexpr
函数 count_if
为结果类型提供额外的模板参数 M
,然后只需确定每个元素的值:
template<size_t I, typename T, typename Pred, std::enable_if_t<I == 0, int> = 0>
constexpr T get_element_helper(T const* arr, Pred&& pred){
return pred(arr[0]) ? arr[0] : get_element_helper<0>(arr + 1, pred);
}
template<size_t I, typename T, typename Pred, std::enable_if_t<I != 0, int> = 0>
constexpr T get_element_helper(T const* arr, Pred&& pred){
return pred(arr[0]) ? get_element_helper<I - 1>(arr + 1, pred) : get_element_helper<I>(arr + 1, pred);
}
template<typename T, size_t N, typename Pred, size_t ...Is>
constexpr std::array<T, sizeof...(Is)> filter_array_helper(std::array<T, N> const& arr, Pred&& pred, std::index_sequence<Is...>*){
return { get_element_helper<Is>(arr.data(), pred)... };
}
template<size_t M, typename T, size_t N, typename Pred>
constexpr std::array<T, M> filter_array(std::array<T, N> const& arr, Pred&& pred){
return filter_array_helper(arr, std::forward<Pred>(pred), (std::make_index_sequence<M>*)nullptr);
}
template<typename T, size_t N, typename Pred, size_t... Is>
constexpr size_t count_if_helper(std::array<T, N> const& arr, Pred&& pred, std::index_sequence<Is...>*){
return ((size_t)(bool)pred(arr[Is]) + ...);
}
template<typename T, size_t N, typename Pred>
constexpr size_t count_if(std::array<T, N> const& arr, Pred&& pred){
return count_if_helper(arr, std::forward<Pred>(pred), (std::make_index_sequence<N>*)nullptr);
};
int main(){
constexpr std::array<int, 5> a = { 0, 1, 2, 3, 4 };
constexpr auto pred = [](int a){ return a % 2 == 0; };
constexpr auto b = filter_array<count_if(a, pred)>(a, pred); // std::array<int, 3>{0, 2, 4}
}
由于<algorithm>
中的很多算法在C++20中是constexpr
,我们可以在meta-programming中自然地使用它:
#include <algorithm>
#include <array>
struct foo {
int f1;
int f2;
bool operator==(const foo&) const = default;
};
constexpr static std::array arr{foo{1,1}, foo{2,2}, foo{3,3}, foo{4,4}, foo{5,5}};
constexpr auto filter_array(auto pred) {
constexpr auto p = [=] {
auto res = arr;
auto size = std::distance(res.begin(), std::remove_if(res.begin(), res.end(), pred));
return std::pair{res, size};
}();
constexpr auto res = p.first;
constexpr auto size = p.second;
return [=]<auto... Is>(std::index_sequence<Is...>) {
return std::array{res[Is]...};
}(std::make_index_sequence<size>{});
}
int main() {
// will result in { foo{1,1}, {2,2}, {4,4}, {5,5} }
constexpr auto filterd = filter_array([](const foo& f) { return f.f1 == 3; });
static_assert(filterd == std::array{foo{1,1}, foo{2,2}, foo{4,4}, foo{5,5}});
}
给定一个 constexpr std::array
和一个 UnaryPredicate pred
,我如何将数组过滤为 return 一个新的 constexpr std::array
,其所有元素都满足 pred(e)
?
struct foo {
int f1;
int f2;
};
static constexpr std::array<foo, 5> arr{ foo{1,1}, {2,2}, {3,3}, {4,4}, {5,5} };
int main() {
// will result in { foo{1,1}, {2,2}, {4,4}, {5,5} }
constexpr auto filterd = filter_array(arr, [](const foo& f) {return f.f1 != 3;})
return 0;
}
肯定是,依赖类型,依赖于值的类型,在present-day C++中是不可能的。您不能仅通过函数参数来决定结果类型 std::array<T, M>
的大小 M
,这意味着 M
必须是模板参数。
因此您必须通过 constexpr
函数 count_if
为结果类型提供额外的模板参数 M
,然后只需确定每个元素的值:
template<size_t I, typename T, typename Pred, std::enable_if_t<I == 0, int> = 0>
constexpr T get_element_helper(T const* arr, Pred&& pred){
return pred(arr[0]) ? arr[0] : get_element_helper<0>(arr + 1, pred);
}
template<size_t I, typename T, typename Pred, std::enable_if_t<I != 0, int> = 0>
constexpr T get_element_helper(T const* arr, Pred&& pred){
return pred(arr[0]) ? get_element_helper<I - 1>(arr + 1, pred) : get_element_helper<I>(arr + 1, pred);
}
template<typename T, size_t N, typename Pred, size_t ...Is>
constexpr std::array<T, sizeof...(Is)> filter_array_helper(std::array<T, N> const& arr, Pred&& pred, std::index_sequence<Is...>*){
return { get_element_helper<Is>(arr.data(), pred)... };
}
template<size_t M, typename T, size_t N, typename Pred>
constexpr std::array<T, M> filter_array(std::array<T, N> const& arr, Pred&& pred){
return filter_array_helper(arr, std::forward<Pred>(pred), (std::make_index_sequence<M>*)nullptr);
}
template<typename T, size_t N, typename Pred, size_t... Is>
constexpr size_t count_if_helper(std::array<T, N> const& arr, Pred&& pred, std::index_sequence<Is...>*){
return ((size_t)(bool)pred(arr[Is]) + ...);
}
template<typename T, size_t N, typename Pred>
constexpr size_t count_if(std::array<T, N> const& arr, Pred&& pred){
return count_if_helper(arr, std::forward<Pred>(pred), (std::make_index_sequence<N>*)nullptr);
};
int main(){
constexpr std::array<int, 5> a = { 0, 1, 2, 3, 4 };
constexpr auto pred = [](int a){ return a % 2 == 0; };
constexpr auto b = filter_array<count_if(a, pred)>(a, pred); // std::array<int, 3>{0, 2, 4}
}
由于<algorithm>
中的很多算法在C++20中是constexpr
,我们可以在meta-programming中自然地使用它:
#include <algorithm>
#include <array>
struct foo {
int f1;
int f2;
bool operator==(const foo&) const = default;
};
constexpr static std::array arr{foo{1,1}, foo{2,2}, foo{3,3}, foo{4,4}, foo{5,5}};
constexpr auto filter_array(auto pred) {
constexpr auto p = [=] {
auto res = arr;
auto size = std::distance(res.begin(), std::remove_if(res.begin(), res.end(), pred));
return std::pair{res, size};
}();
constexpr auto res = p.first;
constexpr auto size = p.second;
return [=]<auto... Is>(std::index_sequence<Is...>) {
return std::array{res[Is]...};
}(std::make_index_sequence<size>{});
}
int main() {
// will result in { foo{1,1}, {2,2}, {4,4}, {5,5} }
constexpr auto filterd = filter_array([](const foo& f) { return f.f1 == 3; });
static_assert(filterd == std::array{foo{1,1}, foo{2,2}, foo{4,4}, foo{5,5}});
}