roger.james
roger.james

Reputation: 1498

Extending this template static_assert code to cover subclasses

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?

Upvotes: 4

Views: 575

Answers (4)

Andy Prowl
Andy Prowl

Reputation: 126432

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.

Upvotes: 3

Vaughn Cato
Vaughn Cato

Reputation: 64308

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> { };

Upvotes: 3

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275370

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.

Upvotes: 1

Kerrek SB
Kerrek SB

Reputation: 476990

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.

Upvotes: 1

Related Questions