Filter template meta function
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.