c++template-meta-programmingstd-ranges

std::views like operation on parameters pack


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?


Solution

  • 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() {}
    

    Demo

    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>>);