From a previous question:
Doing a static_assert that a template type is another template
Andy Prowl provided me with this code that allows me to static_assert
that a template type is another template type:
template<template<typename...> class TT, typename... Ts>
struct is_instantiation_of : public std::false_type { };
template<template<typename...> class TT, typename... Ts>
struct is_instantiation_of<TT, TT<Ts...>> : public std::true_type { };
template<typename T>
struct foo {};
template<typename FooType>
struct bar {
static_assert(is_instantiation_of<foo,FooType>::value, ""); //success
};
int main(int,char**)
{
bar<foo<int>> b; //success
return 0;
}
This works great.
But if I change the code like this to use an alias of foo
, things go bad:
template<template<typename...> class TT, typename... Ts>
struct is_instantiation_of : public std::false_type { };
template<template<typename...> class TT, typename... Ts>
struct is_instantiation_of<TT, TT<Ts...>> : public std::true_type { };
template<typename T>
struct foo {};
//Added: alias for foo
template<typename T>
using foo_alt = foo<T>;
template<typename FooType>
struct bar {
//Changed: want to use foo_alt instead of foo here
static_assert(is_instantiation_of<foo_alt,FooType>::value, ""); //fail
};
int main(int,char**) {
//both of these fail:
bar<foo<int>> b;
bar<foo_alt<int>> b2;
return 0;
}
Can this be solved?
No, it cannot be solved (not without changing the design significantly at least). The problem is that template alias names are not deduced, as mentioned in paragraph 14.5.7/2 of the C++11 Standard:
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template. [ Note: An alias template name is never deduced.—end note ]
The paragraph also provides an example:
[ Example:
template<class T> struct Alloc { / ... / }; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; // same as vector<int, Alloc<int>> v; ... template<template<class> class TT> void f(TT<int>); f(v); // error: Vec not deduced <=== Relevant ...
—end example ]
In your concrete case, the problem is that when trying to match the partial specialization, the compiler won't deduce that your type is an instantiation of foo_alt
(since foo_alt
is the name of an alias template), and the primary template gets picked.
If you want to use alias templates, you will have to give up a bit of genericity and create a type trait specific for foo
:
#include <type_traits>
template<typename T>
struct foo {};
template<typename T>
struct is_instantiation_of_foo : std::false_type { };
template<typename...Ts>
struct is_instantiation_of_foo<foo<Ts...>> : std::true_type { };
Which you could then use this way:
template<typename FooType>
struct bar {
static_assert(is_instantiation_of_foo<FooType>::value, ""); //fail
};
Now, none of the assertions in the following program will fire:
template<typename T>
using foo_alt = foo<T>;
int main(int,char**) {
// None of these fail:
bar<foo<int>> b;
bar<foo_alt<int>> b2;
return 0;
}
Here is a live example.
If you have an alias template that does not alter the template parameters of the referred class (like in your example; it just renames the referred template), then you can use something like (it's not the most elegant way)
template < template<class...> class TT0, template<class...> class TT1,
class... Ts1 >
struct is_from_same_template_helper
{
template < class T = TT0<Ts1...>,
class = typename std::enable_if<
std::is_same<TT0<Ts1...>, TT1<Ts1...>>::value
>::type
>
static std::true_type test(int);
template < class T = int >
static std::false_type test(...);
};
template<template<class...> class, class>
struct is_instantiation_of : public std::false_type { };
template<template<class...> class TT0, template<class...> class TT1,
class... Ts1>
struct is_instantiation_of<TT0, TT1<Ts1...>>
: public decltype( is_from_same_template_helper<TT0, TT1, Ts1...>
::template test<>(0) )
{ };
Thanks to Yakk for pointing out that you need a SFINAE-type of check in order to only let the static_assert
fail (previous version failed due to a compiler error before the static_assert
).
As only the specializations of alias templates are equal to specializations of the referred template; the alias template itself is not equal to the referred template.
If the alias template does alter the template parameters, the implementation above can create false negatives.
As an xy-Problem, you could change your implementation to:
#include <type_traits>
template < template <typename...> class TT0, template <typename...> class TT1 >
struct is_same_template : public std::false_type { };
template < template <typename...> class TT >
struct is_same_template < TT, TT > : public std::true_type { };
template < typename T0, typename T1 >
struct is_from_same_template : public std::false_type { };
template < template <typename...> class TT0, template <typename...> class TT1,
typename... Ts0, typename... Ts1 >
struct is_from_same_template < TT0<Ts0...>, TT1<Ts1...> >
: public is_same_template<TT0, TT1> { };
template<typename T>
struct foo {};
//Added: alias for foo
template<typename T>
using foo_alt = foo<T>;
template<typename FooType>
struct bar {
//Changed: want to use foo_alt instead of foo here
static_assert(is_from_same_template<foo_alt<int>, FooType>::value, "");
};
int main(int,char**) {
//both of these succeed:
bar<foo<int>> b;
bar<foo_alt<int>> b2;
return 0;
}
Of course, you need to have a valid instantiation of foo_alt
for this approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With