How to tell if template type is an instance of a template class?

Viewed 6841

I have a function that takes a template type to determine a return value. Is there any way to tell at compile time if the template type is some instantiation of a template class?

Ex.

class First { /* ... */ };

template <typename T>
class Second { /* ... */ };

using MyType = boost::variant<First, Second<int>, Second<float>>;

template <typename SecondType>
auto func() -> MyType {
    static_assert(/* what goes here?? */, "func() expects Second type");
    SecondType obj;
    // ...
    return obj;
}

MyType obj = func<Second<int>>();

I know it is possible to get around this by doing

template <typename T>
auto func() -> MyType {
    static_assert(std::is_same<T, int>::value || std::is_same<T, float>::value,
                  "func template must be type int or float");

    Second<T> obj;
    // ...
    return obj;
}

MyType obj = func<int>();

I'm just curious in general if there is a way to test if a type is an instantiation of a template class? Because if MyType ends up having 6 Second instantiations, I don't want to have to test for all possible types.

4 Answers

Yet another improvement to the answer of @RichardHodges: Usually, one also wants to capture not only plain types, but rather all cv-qualified and ref-qualified types.

Put differently, if is_instance<A, Second>{} is true, also is_instance<A const&, Second>{} or is_instance<A&&, Second>{} should be true. The current implementations in this thread don't support that.

The following code accounts for all cv-ref-qualified types, by adding another indirection and a std::decay_t:

namespace
{
    template <typename, template <typename...> typename>
    struct is_instance_impl : public std::false_type {};

    template <template <typename...> typename U, typename...Ts>
    struct is_instance_impl<U<Ts...>, U> : public std::true_type {};
}

template <typename T, template <typename ...> typename U>
using is_instance = is_instance_impl<std::decay_t<T>, U>;

Use it as

#include <iostream>

template <typename ...> struct foo{};
template <typename ...> struct bar{};

int main()
{
    std::cout << is_instance<foo<int>, foo>{} << std::endl;           // prints 1
    std::cout << is_instance<foo<float> const&, foo>{} <<std::endl;   // prints 1
    std::cout << is_instance<foo<double,int> &&, foo>{} << std::endl; // prints 1
    std::cout << is_instance<bar<int> &&, foo>{} << std::endl;        // prints 0
}

This works for int template arguments.

template <class, template <int...> class>
struct is_instance : public std::false_type {};

template <int...Ts, template <int...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
Related