I need sometimes to filter some types from a given parameter pack and get the result as a std::tuple
. For instance, it can be a filter based on the index of the types in the pack.
As an example, here is a recursive implementation of a stride-like filter, with a semantic similar to std::views::stride
#include <tuple>
namespace pack
{
template<int R, int IDX, typename...ARGS>
struct stride_impl { };
template<int R, int IDX, typename...ARGS>
using stride_impl_t = typename stride_impl<R,IDX,ARGS...>::type;
template<int R, int IDX, typename T, typename...ARGS>
struct stride_impl<R,IDX,T,ARGS...>
{
using type = std::conditional_t <
IDX%R==0,
decltype(std::tuple_cat (
std::tuple<T> {},
stride_impl_t<R,IDX+1,ARGS...> {}
)),
stride_impl_t<R,IDX+1,ARGS...>
>;
};
template<int R, int IDX>
struct stride_impl<R,IDX> { using type = std::tuple<>; };
template<int R, typename...ARGS>
struct stride
{
static_assert(R>0);
using type = stride_impl_t<R,0,ARGS...>;
};
template<int R, typename ...ARGS>
using stride_t = typename stride<R,ARGS...>::type;
};
template<int R> using example = pack::stride_t<R,char,int,long,float,double>;
static_assert (std::is_same_v< example<1>, std::tuple<char,int,long,float,double>>);
static_assert (std::is_same_v< example<2>, std::tuple<char, long, double>>);
static_assert (std::is_same_v< example<3>, std::tuple<char, float >>);
static_assert (std::is_same_v< example<4>, std::tuple<char, double>>);
static_assert (std::is_same_v< example<5>, std::tuple<char >>);
int main() {}
which feels tedious.
Is it possible to have a more direct implementation that avoids explicit recursion (maybe with fold expression)? Perhaps there is something in std
that covers such filters on parameters packs in a similar way to std::views
?
I eventually found a way to write a pack::view
structure that should generalize to other views than stride. It makes it possible to write something like:
using foo = pack::view_t <std::views::stride(3),char,int,long,float,double>;
where we explicitly use std::views::stride
as a non type template parameter. It is also possible to compose views:
using drop_take_reverse = typename pack::view_t <
std::views::drop(1)
| std::views::take(3)
| std::views::reverse,
char,int,long,float,double
>;
static_assert (std::is_same_v<drop_take_reverse, std::tuple<float,long,int>>);
The idea is to create an constexpr std::array
from the view representing the indexes to be kept and then "transform" the content of the array into a parameter pack (see here for a solution) used for filtering the required types.
#include <tuple>
#include <array>
#include <ranges>
//////////////////////////////////////////////////////////////////////////////////////////
// see https://stackoverflow.com/questions/60434033/how-do-i-expand-a-compile-time-stdarray-into-a-parameter-pack
template <auto arr, template <auto...> typename Consumer, typename IS = decltype(std::make_index_sequence<arr.size()>())>
struct Generator;
template <auto arr, template <auto...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>>
{
using type = Consumer<arr[I]...>;
};
//////////////////////////////////////////////////////////////////////////////////////////
namespace pack
{
template<auto View, typename...ARGS>
struct view
{
// we create a std::array holding the indexes to be kept (no std::ranges::to<std::array<,>> apparently)
constexpr static auto indexes_as_array()
{
// we define the indexes to be kept in the parameters pack.
constexpr auto indexes_view = std::views::iota (size_t{0}, sizeof...(ARGS)) | View ;
std::array<int,indexes_view.size()> arr;
for (auto [i,x] : indexes_view | std::views::enumerate) { arr[i]=x; }
return arr;
}
template <auto... s>
struct ConsumerStruct
{
using type = std::tuple< std::tuple_element_t<s, std::tuple<ARGS...> >...>;
};
// we define our type as an application of the Generator with our ConsumerStruct.
using type = typename Generator<indexes_as_array(), ConsumerStruct>::type::type;
};
template<auto View, typename ...ARGS> using view_t = typename view<View,ARGS...>::type;
}
//////////////////////////////////////////////////////////////////////////////////////////
template<int R>
using stride = typename pack::view_t <std::views::stride(R),char,int,long,float,double>;
static_assert (std::is_same_v< stride<1>, std::tuple<char,int,long,float,double>>);
static_assert (std::is_same_v< stride<2>, std::tuple<char, long, double>>);
static_assert (std::is_same_v< stride<3>, std::tuple<char, float >>);
static_assert (std::is_same_v< stride<4>, std::tuple<char, double>>);
static_assert (std::is_same_v< stride<5>, std::tuple<char >>);
//////////////////////////////////////////////////////////////////////////////////////////
template<int R>
using drop = typename pack::view_t <std::views::drop(R),char,int,long,float,double>;
static_assert (std::is_same_v< drop<0>, std::tuple<char,int,long,float,double>>);
static_assert (std::is_same_v< drop<1>, std::tuple< int,long,float,double>>);
static_assert (std::is_same_v< drop<2>, std::tuple< long,float,double>>);
static_assert (std::is_same_v< drop<3>, std::tuple< float,double>>);
static_assert (std::is_same_v< drop<4>, std::tuple< double>>);
static_assert (std::is_same_v< drop<5>, std::tuple< >>);
//////////////////////////////////////////////////////////////////////////////////////////
template<int R>
using take = typename pack::view_t <std::views::take(R),char,int,long,float,double>;
static_assert (std::is_same_v< take<5>, std::tuple<char,int,long,float,double>>);
static_assert (std::is_same_v< take<4>, std::tuple<char,int,long,float >>);
static_assert (std::is_same_v< take<3>, std::tuple<char,int,long >>);
static_assert (std::is_same_v< take<2>, std::tuple<char,int >>);
static_assert (std::is_same_v< take<1>, std::tuple<char >>);
static_assert (std::is_same_v< take<0>, std::tuple< >>);
//////////////////////////////////////////////////////////////////////////////////////////
template<int A, int B>
using drop_take_reverse = typename pack::view_t <std::views::drop(A) | std::views::take(B) | std::views::reverse, char,int,long,float,double>;
static_assert (std::is_same_v<drop_take_reverse<1,3>, std::tuple<float,long,int>>);
//////////////////////////////////////////////////////////////////////////////////////////
int main() {}
A limitation is that we need to call size
one the view in order to know the size of the array to be created, so views like std::views::filter
can't be used in this context.
UPDATE
We can go beyong this limitation with a trick: we compute an array of size sizeof...(ARGS)
and fill it with the items of the view. We also remember the actual number of items of the view => it is used later in order to shrink the indexes array to the correct size.
Demo
So we can also write something like:
using filtereven = pack::view_t<std::views::filter([](int n) {return n%2==0;}), char,int,long,float,double>;
static_assert (std::is_same_v<filtereven, std::tuple<char,long,double>>);