16

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.

vvv444
  • 2,764
  • 1
  • 14
  • 25
Cameron Rowe
  • 223
  • 2
  • 5
  • Can you change `Second`? This is easy if you can add an `std::true_type isSecond` member – Caleth May 16 '17 at 23:07
  • Possible dupe [Is there a way using c++ type traits to check if a type is a template and any properties thereof?](http://stackoverflow.com/questions/13919234/is-there-a-way-using-c-type-traits-to-check-if-a-type-is-a-template-and-any-pr)? – George May 16 '17 at 23:24

4 Answers4

21

Here's an option:

#include <iostream>
#include <type_traits>
#include <string>

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

template <class T, template <class> class U>
struct is_instance<U<T>, U> : public std::true_type {};

template <class>
class Second 
{};

int main()
{
    using A = Second<int>;
    using B = Second<std::string>;
    using C = float;
    std::cout << is_instance<A, Second>{} << '\n'; // prints 1
    std::cout << is_instance<B, Second>{} << '\n'; // prints 1
    std::cout << is_instance<C, Second>{} << '\n'; // prints 0
}

It's basically specializing the is_instance struct for types that are instantiations of a template.

Fatih BAKIR
  • 4,569
  • 1
  • 21
  • 27
  • 7
    Better make it `template class> struct is_instance`. This way you are not restricted to templates with only one parameter. Also you should note that this only works for templates with typename arguments. – Henri Menke May 16 '17 at 23:30
11

Another option, picking up on Henri's comment:

#include <iostream>
#include <type_traits>
#include <string>

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

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

template <class>
class Second 
{};

template <class, class, class>
class Third 
{};

int main()
{
    using A = Second<int>;
    using B = Second<std::string>;
    using C = float;
    using D = Third<std::string, int, void>;
    std::cout << is_instance<A, Second>{} << '\n'; // prints 1
    std::cout << is_instance<B, Second>{} << '\n'; // prints 1
    std::cout << is_instance<C, Second>{} << '\n'; // prints 0
    std::cout << is_instance<D, Third>{} << '\n'; // prints 1
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    This doesn't seem to work when template arguments are of integral types. e.g. `is_instance, std::array>` doesn't compile. Do you know how to solve this? – user3520616 Nov 05 '19 at 14:13
  • 2
    @user3520616 the problem is that 3 is an integer which is a non-type template parameter. we'd have to promote it to a type (e.g. std::integral_constant). This would mean providing a specialisation for std::array, or increasing the complexity of is_instance. I'll have a think. – Richard Hodges Nov 05 '19 at 14:37
8

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::remove_cvref_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::remove_cvref_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
}
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • I upvoted, but to be consistent with e.g. `std::is_base_of`, I'd use `std::remove_cv_t` instead of `std::decay_t`. `std::is_base_of` won't consider a reference to be a base or derived type of another. – Max Jul 14 '21 at 12:17
  • I have no clear intuition for this case, i.e. it's not clear to me what most of the users would require most of the time. I know that I often had to decay the input to `std::is_base_of` in my expression template-based libraries. But you're right, maybe it's better to stick to a common behavior and ignore ref-qualified types. – davidhigh Jul 14 '21 at 21:48
  • Any idea how to add `is_instance_v` version of this? – Youda008 Apr 26 '22 at 08:53
  • @Youda008: `is_instance` basically is what is called "_v" in the standard library. Otherwise you'd have to use `::value` somewhere. So, in the standard library, my `is_instance_impl` would have been called `is_instance`, and my `is_instance` would have been called `is_instance_v` in the standard lib – davidhigh Apr 26 '22 at 10:00
  • @Max: In C++20 we now have `std::remove_cvref`, which is basically what I wanted with `std::decay` – davidhigh Apr 26 '22 at 13:36
  • @davidhigh But you have to use `::value` in your example. Your `is_instance` produces either `std::false_type` or `std::true_type` both of which are structs with member `value`. A `_v` variant would have to be declared as `inline constexpr bool ...` and not `using ... =`. – Youda008 Apr 27 '22 at 12:42
  • @Youda008: yes, that's right. So you would have to add another "_v" variant, and I guess you know how to do it by now :-) – davidhigh Apr 27 '22 at 18:24
1

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 {};
TomSaw
  • 11
  • 4