4

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;
  return 0;
}

This works great.

But this does not work for a subclass of foo<whatever>:

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, ""); //fail
};

//Added: Subclass of foo<int>
struct foo_sub : foo<int> {
};

int main(int,char**)
{
  bar<foo_sub> b; //Changed: Using the subclass
  return 0;
}

Can Andy Prowl's is_instantiation_of code be extended to allow for subclasses?

Community
  • 1
  • 1
roger.james
  • 1,478
  • 1
  • 11
  • 23
  • possible duplicate of [Using a template alias instead of a template within a template](http://stackoverflow.com/questions/17392621/using-a-template-alias-instead-of-a-template-within-a-template) – Kerrek SB Jun 30 '13 at 23:18
  • @KerrekSB This is a completely different question (but same introduction) – roger.james Jun 30 '13 at 23:19
  • Couldn't you just have made it a self-contained, *short* question, like "Is it possible to check whether a class is derived from a template instance"? – Kerrek SB Jun 30 '13 at 23:29
  • My answer seems to handle the template alias issue as well. – Vaughn Cato Jul 01 '13 at 04:06

4 Answers4

3

As KerrekSB wrote in his answer, an equally general extension of the solution you posted cannot be achieved.

However, if it is OK for you to give up a bit of genericity, you can write a type trait specific for foo (exploiting the fact that derived-to-base is one of the few conversions that are performed during type deduction):

#include <type_traits>

template<typename T>
struct foo {};

template<typename T>
constexpr std::true_type test(foo<T> const&);

constexpr std::false_type test(...);

template<typename T>
struct is_instantiation_of_foo : public decltype(test(std::declval<T>())) { };

You would then use it like so:

template<typename FooType>
struct bar {
  static_assert(is_instantiation_of_foo<FooType>::value, "");
};

struct foo_sub : foo<int> {
};

int main(int,char**)
{
  bar<foo_sub> b; // Will not fire
  return 0;
}

Here is a live example.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I'm thinking about giving up the genericity and repeat this on a case by case basis, seems much simpler. But Vaughn Cato has claimed that his solution is general and solves both the subclass and alias issue. What do you think? – roger.james Jul 01 '13 at 10:19
  • Your solution does not work if the base class has a pure virtual function and the subclass overrides it. Can this be fixed? Live example: http://coliru.stacked-crooked.com/view?id=ad01ac1f793231c9ae9a3869d9581645-a63ad9fea3739cb794a74965e5d28fb3 – roger.james Jul 01 '13 at 16:23
  • 1
    @roger.james: Yes, just have the `test` function accept by reference to `const` instead of accepting by value. I updated the answer. Here is a [live example](http://coliru.stacked-crooked.com/view?id=72e2d6394ca2c41e3116ed240250a235-a63ad9fea3739cb794a74965e5d28fb3). – Andy Prowl Jul 01 '13 at 16:27
3

This seems to work in many cases:

#include <iostream>
#include <utility>

template <typename T> struct foo { };

struct foo_sub : foo<int> { };

// A fallback function that will only be used if there is no other choice
template< template <typename> class X >
std::false_type isX(...)
{
  return std::false_type();
}

// Our function which recognizes any type that is an instantiation of X or 
// something derived from it.
template< template <typename> class X, typename T >
std::true_type isX(const X<T> &)
{
  return std::true_type();
}

// Now we can make a template whose value member's type is based
// the return type of isX(t), where t is an instance of type T.
// Use std::declval to get a dummy instance of T.
template <template <typename> class X,typename T>
struct is_instantiation_of {
  static decltype(isX<X>(std::declval<T>())) value;
};

template <typename FooType>
struct bar {
  static_assert(is_instantiation_of<foo,FooType>::value,"");
};

int main(int,char**)
{
  //bar<int> a;  // fails the static_assert
  bar<foo<int>> b;  // works
  bar<foo_sub> c;  // works
  return 0;
}

As noted by Yakk, one place that it doesn't work is if you have a class derived from multiple instantiations of foo, such as

struct foo_sub2 : foo<int>, foo<double> { };
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • @Yakk: sorry, I don't understand your comment. Are you asking if `bar` works? – Vaughn Cato Jul 01 '13 at 12:39
  • `(...)` won't accept some types, if I remember rightly (but that might just be a warning). And a type `struct x:foo,foo{};` will have ambiguity problems. – Yakk - Adam Nevraumont Jul 01 '13 at 12:43
  • @Yakk: With g++ 4.7.2 -W -Wall, I don't get a warning from `isX(...)` using std::string. Agreed there is an issue with ambiguity. – Vaughn Cato Jul 01 '13 at 13:19
  • 5.2.2.7 -- in C++03 it is undefined, in C++11 it is conditionally defined with undefined semantics(!). Replace it with vaiardic `Ts const&...` and I think you step away from that scary verbage while still matching worse than the other overload? Or pass a pointer and match on that. – Yakk - Adam Nevraumont Jul 01 '13 at 17:57
1

You can't do that in C++11. You would essentially have to quantify over all class types and check if any of them is a base of the candidate.

There was a proposal in TR2 (which I hear is now defunct), possibly making it into C++14, to add traits std::bases and std::direct_bases which enumerate base classes of a given class, and thus effectively solve your problem (i.e. apply your existing trait to each base class).

GCC does provide this trait in <tr2/type_traits>, if that helps.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
1

I am on my phone, so this might not work.

The goal is to use SFINAE and overloading to ask the question "is there a base class that matches this compile time traits question?"

template<template<typename>class Test>
struct helper {
   static std::false_type test(...);
   template<typename T, typename=typename std::enable_if< Test<T>::value >
   static std::true_type test(T const&);
};
template<template<typename>class Test, typename T, typename=void>
struct exactly_one_base_matches :std::false_type {};
template<template<typename>class Test, typename T>
struct exactly_one_base_matches<Test,T,
  typename std::enable_if<decltype(helper<Test>::test(std::declval<T>()))::value>::type>
:std::true_type {};

If that does not work for a generic test, one where test does pattern matching might. ... might need to be replaced. I cannot think of a way to deal with multiple bases that pass the test...

I think we can do better. There are three possible results from calling the above.

First, one parent or self matches the test. Second, it matches the catch-all. Third, it is ambiguous because it could pass the test in more than one way.

If we improve the catch-all to catch everything at low priority (Ts...&& maybe), we can make failure to compile a success condition.

Return true from SFINAE, true from match-one, and false from catch-all match-none.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524