Filter template meta function I wrote a simple C++ template meta function for filtering out types from a list not matching a predicate. This is similar to Filtering variadic template arguments but does pattern matching instead of std::conditional with the aim of being faster. To that end no empty typelists are concatenated which should incur less instantiations and be faster according to https://blog.galowicz.de/2016/06/25/cpp_template_type_list_performance

The below code works for all my test cases. Any hints how to make it faster (less template instantiations), smaller, better, ...? (Note: Assume appropriate *_t using-templates)

template &lt;typename... Ts&gt;
struct typelist;

template &lt;typename... TLists&gt;
struct concat;
template &lt;typename... Args1, typename... Args2&gt;
struct concat&lt;typelist&lt;Args1...&gt;, typelist&lt;Args2...&gt;&gt;
{
    using type = typelist&lt;Args1..., Args2...&gt;;
};
template &lt;class Seq1, class Seq2, class... Seqs&gt;
struct concat&lt;Seq1, Seq2, Seqs...&gt; : concat&lt;Seq1, concat_t&lt;Seq2, Seqs...&gt;&gt;
{
};

template &lt;class TList, template &lt;typename&gt; class Cond&gt;
struct filter;
template&lt;bool matches, template &lt;typename&gt; class Cond, typename...&gt;
struct filter_helper;

template&lt;template &lt;typename&gt; class Cond, typename T, typename... Ts&gt;
struct filter_helper&lt;true, Cond, T, Ts...&gt;
{
    using type = concat_t&lt;typelist&lt;T&gt;, filter_t&lt;typelist&lt;Ts...&gt;, Cond&gt;&gt;;
};

template&lt;template &lt;typename&gt; class Cond, typename T, typename... Ts&gt;
struct filter_helper&lt;false, Cond, T, Ts...&gt;
{
    using type = filter_t&lt;typelist&lt;Ts...&gt;, Cond&gt;;
};

template&lt;template &lt;typename&gt; class Cond, typename T, typename... Ts&gt;
struct filter&lt;typelist&lt;T, Ts...&gt;, Cond&gt;
{
    using type = typename filter_helper&lt;Cond&lt;T&gt;::value, Cond, T, Ts...&gt;::type;
};

template&lt;template &lt;typename&gt; class Cond&gt;
struct filter&lt;typelist&lt;&gt;, Cond&gt;
{
    using type = typelist&lt;&gt;;
};


Some tests (TMP_ASSERT is like static_assert):

using list1 = tmp::typelist&lt;int, double&gt;;
using list2 = tmp::typelist&lt;float&gt;;
using list3 = tmp::typelist&lt;double&gt;;
using list4 = tmp::typelist&lt;float, double, double&gt;;
using list5 = tmp::typelist&lt;double, float, double&gt;;
using list6 = tmp::typelist&lt;double, double, float&gt;;
using empty = tmp::typelist&lt;&gt;;

template&lt;typename T&gt;
struct is_not_double: std::true_type{};
template&lt;&gt;
struct is_not_double&lt;double&gt;: std::false_type{};

TMP_ASSERT_SAME((tmp::filter_t&lt;list1, is_not_double&gt;), (tmp::typelist&lt;int&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;list2, is_not_double&gt;), (tmp::typelist&lt;float&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;list3, is_not_double&gt;), (tmp::typelist&lt;&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;list4, is_not_double&gt;), (tmp::typelist&lt;float&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;list5, is_not_double&gt;), (tmp::typelist&lt;float&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;list6, is_not_double&gt;), (tmp::typelist&lt;float&gt;));
TMP_ASSERT_SAME((tmp::filter_t&lt;empty, is_not_double&gt;), (tmp::typelist&lt;&gt;));


Note: Code is from a library licensed under BSD 3-Clause.
I find all those structs tricks a bit old-school now...

There is an alternative implementation using constexpr functions; I also recommend to use if constexpr to make things more readable, but it's doable without it; you just need another overload:

template &lt;typename...&gt;
struct Type_list{};

template &lt;typename... T1s, typename... T2s&gt;
constexpr auto concatenate(Type_list&lt;T1s...&gt;, Type_list&lt;T2s...&gt;) {
    return Type_list&lt;T1s..., T2s...&gt;{};
}

template &lt;template &lt;typename&gt; typename Condition, typename Result&gt;
constexpr auto filter_types(Result result, Type_list&lt;&gt;) {
    return result;
}

template &lt;template &lt;typename&gt; typename Condition, typename Result, typename T, typename... Ts&gt;
constexpr auto filter_types(Result result, Type_list&lt;T, Ts...&gt;) {
    if constexpr (Condition&lt;T&gt;{})
        return filter_types&lt;Condition&gt;(concatenate(result, Type_list&lt;T&gt;{}), Type_list&lt;Ts...&gt;{});
    else
        return filter_types&lt;Condition&gt;(result, Type_list&lt;Ts...&gt;{});
}

template &lt;template &lt;typename&gt; typename Condition, typename... Types&gt;
using filtered_types = std::decay_t&lt;decltype(filter_types&lt;Condition&gt;(Type_list&lt;&gt;{}, Type_list&lt;Types...&gt;{}))&gt;;


template&lt;typename T&gt;
struct is_not_double: std::true_type{};
template&lt;&gt;
struct is_not_double&lt;double&gt;: std::false_type{};

template &lt;typename T&gt;
void print_type() {
    puts(__PRETTY_FUNCTION__);
    }

int main() {
    print_type&lt;filtered_types&lt;is_not_double, double, int, char, float*, double, char*, double&gt;&gt;();
}


I'd rather have this sort of programming: template programming looks like normal programming, rather than have two subsets of the language apart from each other.



Edit: as per request, here's a rewriting of the code without C++ 17 features:

template &lt;typename...&gt;
struct Type_list{};

template &lt;typename... T1s, typename... T2s&gt;
constexpr auto concatenate(Type_list&lt;T1s...&gt;, Type_list&lt;T2s...&gt;) {
    return Type_list&lt;T1s..., T2s...&gt;{};
}

template &lt;template &lt;typename&gt; typename Condition, typename Result, typename T, typename... Ts&gt;
constexpr auto filter_types(Result result, Type_list&lt;T, Ts...&gt;, std::true_type)  {
    return filter_types&lt;Condition&gt;(concatenate(result, Type_list&lt;T&gt;{}), Type_list&lt;Ts...&gt;{});
}

template &lt;template &lt;typename&gt; typename Condition, typename Result, typename T, typename... Ts&gt;
constexpr auto filter_types(Result result, Type_list&lt;T, Ts...&gt;, std::false_type) {
    return filter_types&lt;Condition&gt;(result, Type_list&lt;Ts...&gt;{});
}

template &lt;template &lt;typename&gt; typename Condition, typename Result&gt;
constexpr auto filter_types(Result result, Type_list&lt;&gt;) {
    return result;
}

template &lt;template &lt;typename&gt; typename Condition, typename T, typename... Ts, typename... Us&gt;
constexpr auto filter_types(Type_list&lt;Us...&gt; result, Type_list&lt;T, Ts...&gt; lst) {
    return filter_types&lt;Condition&gt;(result, lst, Condition&lt;T&gt;{});
}

template &lt;template &lt;typename&gt; typename Condition, typename... Types&gt;
using type_filter = decltype(filter_types&lt;Condition&gt;(Type_list&lt;&gt;{}, Type_list&lt;Types...&gt;{}));


As for a variadic, container agnostic, concatenate, here's what you would do with fold expressions:

template &lt;template &lt;typename&gt; typename Type_container, typename... T1s, typename... T2s&gt;
constexpr auto operator+(Type_container&lt;T1s...&gt;, Type_container&lt;T2s...&gt;) {
    return Type_container&lt;T1s..., T2s...&gt;{};
}

template &lt;typename... Type_lists&gt;
constexpr auto concatenate(Type_lists... type_lists) {
    return (type_lists + ...);
}


Without fold expressions you need to provide overloads for calls with 0, 1 and more type lists, as you did with your structs.