14

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?

Community
  • 1
  • 1
roger.james
  • 1,478
  • 1
  • 11
  • 23
  • Hmm, looks like `foo_alt` is a *typedef-name* and not a *template-name*... but that only affects your `bar`; you can keep `main` as it is. – Kerrek SB Jun 30 '13 at 17:58
  • I think you could check if two types are instantiations of the same template, this also works with alias templates. – dyp Jun 30 '13 at 18:17

2 Answers2

11

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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 1
    Maybe we can do better. `SFINAE`-safe pattern match deduce the `template` `M` such that the passed in type is `M` for some `Ts...`. Then check `std::is_same,M>`, also SFINAE guarded in case that `X` is ill formed, where `X<>` is the `template` passed in to match... – Yakk - Adam Nevraumont Jun 30 '13 at 18:15
  • @Yakk: You're too genius for me :) I don't understand what "SFINAE-deduce` means, and I'm afraid I would need to stare at the solution a few minutes before I get it anyway ;) – Andy Prowl Jun 30 '13 at 18:18
  • see dyn's answer: I was less than clear, and writing code on this keyboard is hard. I meant "deduce in a SFINAE safe way", so if it failed to match it would just say `false`. Dyn spotted the flaw I missed: he's too smart for me! – Yakk - Adam Nevraumont Jun 30 '13 at 18:32
  • @Yakk No he's not too smart, but he's got a P at the end ;) – dyp Jun 30 '13 at 18:33
  • @Yakk You were right that some guard is required to check if `X` is ill-formed. Updated my answer. – dyp Jun 30 '13 at 18:55
1

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.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • I can't understand what is the purpose of `TT1` template parameter of `is_from_same_template_helper` template. – Constructor Apr 06 '14 at 12:48
  • @Constructor As I "announced" in my previous comment here, there's been a bug in the code (introduced according to the edit history when changing from hard to SFINAE error). Is it clear now? – dyp Apr 06 '14 at 12:52
  • Oh, sorry, I didn't notice your comment. It is clear now, thank you. As I see you can't decide which keyword you like more (`typename` or `class`). :-) – Constructor Apr 06 '14 at 12:55