Search code examples
c++templatesc++11variadic-templates

How to filter a variadic template pack by type derivation?


I have a template class receiving multiple types, each type received is subclass of one of two options. I want to expand them differently depending the parent class identifying each of them. This is equivalent to implement "filter" over the variadic template parameters.

For example:

class A{};
class B{};

template<class... C>
struct F{
     std::tuple<types_derived_by<A, C>...> fn(types_subclassing<B, C>...){}
};

The types_derived_by template function should produce a variadic template pack with all the types in C that are derived from A, or B.

For example:

struct DA : public A{};
struct DB : public B{};
int main(){
    F<DA, DB> f;
    //f has a member function: DA fn(DB);

}

I'm using C++11, but I'm ok to move to c++14 if necessary.


Solution

  • You may do something like:

    template <template <typename> class Pred, typename TUPLE, typename Res = std::tuple<>>
    struct Filter;
    
    template <template <typename> class Pred, typename Res> 
    struct Filter<Pred, std::tuple<>, Res>
    {
        using type = Res;
    };
    
    template <template <typename> class Pred, typename T, typename ... Ts, typename ... TRes> 
    struct Filter<Pred, std::tuple<T, Ts...>, std::tuple<TRes...>> :
        Filter<Pred,
               std::tuple<Ts...>,
               std::conditional_t<Pred<T>::value,
                                  std::tuple<TRes..., T>,
                                  std::tuple<TRes...>>>
    {
    };
    

    or alternatively, using std::tuple_cat

    template <template <typename> class Pred, typename TUPLE>
    struct Filter;
    
    template <template <typename> class Pred, typename ... Ts> 
    struct Filter<Pred, std::tuple<Ts...>>
    {
        using type = decltype(std::tuple_cat(
               std::declval<std::conditional_t<Pred<Ts>::value,
                                               std::tuple<Ts>,
                                               std::tuple<>>>()...));
    };
    

    and then:

    class A {};
    template <typename T>
    using is_base_of_A = std::is_base_of<A, T>;
    
    class B {};
    struct DA : public A{};
    struct DB : public B{};
    struct DA1 : public A{};
    
    static_assert(std::is_same<std::tuple<DA, DA1>,
                               Filter<is_base_of_A, std::tuple<DA, DB, DA1>>::type>::value,
                  "unexpected");
    

    Demo / Demo with std::tuple_cat