Implementation
We would like to implement a type trait is_instance_of_v<T,U> that, for a given type T and type template U, gives true if T is an instantiation of U, and false otherwise. The following illustrates the desired behavior:
is_instance_of_v<std::vector<int>,std::vector>; // true
is_instance_of_v<std::vector<int>,std::list>; // falseFor templates with one template parameter, we could implement is_instance_of_v as follows:
template<class T, template<class> class U>
inline constexpr bool is_instance_of_v = std::false_type{};
template<template<class> class U, class V>
inline constexpr bool is_instance_of_v<U<V>,U> = std::true_type{};This features the curious (rare?) use of a template template parameter (yes!): a template parameter that accepts a template. This is the template<class> class U part. The declaration of is_instance_of_v (the first statement) establishes that the trait is false in the general case, then the specialization of is_instance_of_v (the second statement) establishes that it is true in the special case that the type is indeed an instantiation of the type template.
That is for one template parameter. It is readily extended to more than one by using a parameter pack:
template<class T, template<class...> class U>
inline constexpr bool is_instance_of_v = std::false_type{};
template<template<class...> class U, class... Vs>
inline constexpr bool is_instance_of_v<U<Vs...>,U> = std::true_type{};The ... is the parameter pack. This latter version is the preferred implementation, replacing the former version.
Demonstration
Motivated by the previous post on overloading functions with forwarding references, we can use this in place of std::is_same_v where we wish to check that the type is an instantiation of a given template, not just an exact match against some other type. The caveats around const and ref qualifiers still apply, however, and the implementation of is_instance_of_v above does not consider const or ref-qualified types to be instantiations of a template. Hence the following:
is_instance_of_v<std::vector<int>,std::vector>; // true
is_instance_of_v<std::vector<int>&,std::vector>; // false
is_instance_of_v<std::vector<int>&&,std::vector>; // false
is_instance_of_v<const std::vector<int>,std::vector>; // false
is_instance_of_v<const std::vector<int>&,std::vector>; // falseWe can use std::decay_t around the type (as suggested in the previous post) to remove const and ref qualifiers:
is_instance_of_v<std::decay_t<std::vector<int>>,std::vector>; // true
is_instance_of_v<std::decay_t<std::vector<int>&>,std::vector>; // true
is_instance_of_v<std::decay_t<std::vector<int>&&>,std::vector>; // true
is_instance_of_v<std::decay_t<const std::vector<int>>,std::vector>; // true
is_instance_of_v<std::decay_t<const std::vector<int>&>,std::vector>; // trueOr decltype(...) works too:
std::vector<int> x;
is_instance_of_v<decltype(x),std::vector>; // trueThat’s because while x has lvalue reference type std::vector<int>&, its decltype is still just std::vector<int>.
Summary
This post introduced a type trait is_instance_of_v<T,U> to check if some type T is an instantiation of some type template U. It can be used for various purposes, but might be particularly useful in the context of implementing function overloads with forwarding reference parameters as suggested by the previous post.
Here’s a complete source file for further play:
#include <type_traits>
#include <iostream>
#include <vector>
#include <list>
template<class T, template<class...> class U>
inline constexpr bool is_instance_of_v = std::false_type{};
template<template<class...> class U, class... Vs>
inline constexpr bool is_instance_of_v<U<Vs...>,U> = std::true_type{};
int main() {
std::cerr << is_instance_of_v<std::vector<int>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::vector<int>,std::list> << std::endl; // false
std::cerr << is_instance_of_v<std::vector<int>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::vector<int>&,std::vector> << ' '; // false
std::cerr << is_instance_of_v<std::vector<int>&&,std::vector> << ' '; // false
std::cerr << is_instance_of_v<const std::vector<int>,std::vector> << ' '; // false
std::cerr << is_instance_of_v<const std::vector<int>&,std::vector> << std::endl; // false
std::cerr << is_instance_of_v<std::decay_t<std::vector<int>>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::decay_t<std::vector<int>&>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::decay_t<std::vector<int>&&>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::decay_t<const std::vector<int>>,std::vector> << ' '; // true
std::cerr << is_instance_of_v<std::decay_t<const std::vector<int>&>,std::vector> << std::endl; // true
std::vector<int> x;
std::cerr << is_instance_of_v<decltype(x),std::vector> << ' '; // true
return 0;
}