Sergey Kolesnik
Sergey Kolesnik

Reputation: 3640

Filter a tuple list of types given a template template predicate

I was going to ask a question, but have found an answer on my own while writing it. The question is how to filter a tuple given a template template predicate without specializing it.

Example usage:

    using tuple_list_t = std::tuple<std::string, int, none, double, char, abc, bool>;
    using tuple_found_expected_t = std::tuple<int, double, char, bool>;

    // filtering a tuple 
    using tuple_filtered_t = filter_t<tuple_list_t, std::is_fundamental>;

    static_assert(std::is_same<tuple_filtered_t, tuple_found_expected_t>(), "");

If someone have any advices or remarks about the problem, please, write an answer or a comment.

Upvotes: 0

Views: 313

Answers (2)

max66
max66

Reputation: 66200

Sorry but your solution seems to me over-complicated. Particularly regarding the SFINAE part (why the void?).

What about simply as follows?

template <typename, template <typename...> class>
struct filter;

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

template <typename Tpl, 
          template <typename...> class Pred>
using filter_t = typename filter<Tpl, Pred>::type;

Upvotes: 1

Sergey Kolesnik
Sergey Kolesnik

Reputation: 3640

Here is a version of a template SFINAE struct to filter an std::tuple of types given a SFINAE predicate. So the invocation looks just like using an STL algorithm with an unary predicate.

Example:

    using tuple_list_t = std::tuple<std::string, int, none, double, char, abc, bool>;
    using tuple_found_expected_t = std::tuple<int, double, char, bool>;

    // filtering a tuple 
    using tuple_filtered_t = filter_t<tuple_list_t, std::is_fundamental>;

    static_assert(std::is_same<tuple_filtered_t, tuple_found_expected_t>(), "");

There were two major problems that I have faced.

  1. How to pass a template as a template template parameter without specializing it:
    eld::filter<tuple, std::is_fundamental>. Here std::is_fundamental is a template itself and expects to be specialized. But I can't specialize it before the invocation - I need to specialize it later with the types from a tuple.
    Solution is simple. Just have a stub void (for example) default specializtion:
template <typename Tuple, template <typename> class SFINAEPredicate, typename = void>
  1. Removing types that yield false with SFINAEPredicate<T>::value.
    Now there is a way to use recursive SFINAE template specialization, but it requires a lot of typing and possibly a lot of redundant code generation. The latter is a big concern when compiling in debug, since MinGW will fail with assembler error file too big and up to this point I have found no working way to circumvent this problem (optimization pragmas don't do nothing). But that is out of scope of this question/answer.
    The way I did it was with using type = decltype(std::tuple_cat(append_if_t<std::tuple<>, Types, SFINAEPredicate>()...));
    append_if_t returns a tuple, which will be empty in some cases, and then all of them are concatenated in a final tuple with an std::tuple_cat.
    So, there are N specializations for append_if_t which can be reused by the compiler and no recursive intermediate specializations.

Here is the code:

        template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/, typename = T>
        struct append_if;

        template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
        struct append_if<std::tuple<Types...>, T, SFINAEPredicate, T>
        {
            using type = typename std::conditional<SFINAEPredicate<T>::value,
                    std::tuple<Types..., T>, std::tuple<Types...>>::type;
        };

        template<typename Tuple, typename T, template<typename> class SFINAEPredicate, typename = T>
        using append_if_t = typename append_if<Tuple, T, SFINAEPredicate>::type;

        // TODO: function to traverse a tuple and find a type (index) that can be used to initialize a POD element
        // pass a template parameter (trait validator) that accepts a type

        // template for find_if in tuple
        template<typename Tuple, template<typename> class /*SFINAEPredicate*/, typename = void>
        struct filter;

        template<template<typename> class SFINAEPredicate, typename ... Types>
        struct filter<std::tuple<Types...>, SFINAEPredicate, void>
        {
            using type = decltype(std::tuple_cat(append_if_t<std::tuple<>, Types, SFINAEPredicate>()...));
        };

        template<typename Tuple, template<typename> class SFINAEPredicate, typename = void>
        using filter_t = typename filter<Tuple, SFINAEPredicate>::type;

Upvotes: 0

Related Questions