voltrevo
voltrevo

Reputation: 10459

Is it possible to check at compile time whether a type is derived from some instantiation of a template?

I would like to write a template function which behaves one way if the passed type is derived from any template instantiation of another class, and another way if not.

I think the code below captures what I would like to do. Unfortunately Caller prints "generic" for both double and Derived.

#include <iostream>

template <typename T>
struct Base
{
};

struct Derived
:
    public Base<int>
{
};

template <typename T>
void Foo(const T&)
{
    std::cout << "generic" << std::endl;
}

template <typename T>
void Foo(const Base<T>&)
{
    std::cout << "derives from Base<T>" << std::endl;
}

template <typename T>
void Caller(const T& t)
{
    Foo(t);
}

int main()
{
    double x;
    Caller(x);

    Derived d;
    Caller(d);

    return 0;
}

(Note that Caller doesn't know which instantiation of Base that its parameter might derive from.)

Upvotes: 4

Views: 1538

Answers (3)

thokra
thokra

Reputation: 2904

If you're able to use C++11 (or <type_traits> in general), the following is also a possible solution and covers not only types T : Base<T>, i.e. instances of the CRTP, but also T : Base<U> without another base class, as requested in your example.

#include <iostream>
#include <type_traits>

template <typename T>
struct Base
{
  typedef T base_value_type;
};

struct Derived : public Base<int>
{
};

template <typename T, typename = T>
struct IsDerived
{
  static const bool value = false;
};

template <typename T>
struct IsDerived<T, typename std::enable_if<std::is_base_of<Base<typename T::base_value_type>, T>::value, T>::type>
{
  static const bool value = true;
};


template <typename T>
void Caller(const T&)
{
  std::cout << IsDerived<T>::value << std::endl;
}

int main()
{
  Caller(double());  // false
  Caller(Derived()); // true

  return 0;
}

Note the typedef T base_value_type - which might be called whatever your like. The idea is that each type T derived from Base<U> can leverage the knowledge of the base's template parameter. It doesn't matter if T == U or not. Trying to substitute the second parameter will fail as soon as you pass in a T that has no typedef T base_value_type and thus no specialization for this particular T will be generated.

EDIT: After processing your comment, and inspired by the thread I posted, I tried to somehow extract some base parameter U when examining some time type T : Base<U>. I don't think this can be done in the way you want, i.e. you pass whatever T and you extract U. However, you can do two things.

Simple Solution: If you have control over how derived classes are implemented, instead of adding a typedef in the base class, simply add a corresponding typedef in the derived class:

template <typename BaseParamType>
class Derived : public Base<BaseParamType>
{
public:
  typedef BaseParamType base_param_type;
}

or, if you don't want derived classes to be class templates as well, simply hard code the type right into the type (you already know the type of the base parameter):

class Derived : public Base<int>
{
public:
  typedef int base_param_type;
}

More involved solution: What you can do, at least for an expected subset of possible Us, is the following:

template <typename DerivedType,
          typename BaseParamType = DerivedType,
          bool   = std::is_base_of<Base<BaseParamType>, DerivedType>::value>
struct Extract
{
  typedef BaseParamType type;
};

template <typename T, typename U>
struct Extract<T, U, false>;

int main()
{
  Extract<DerivedCRTP>::type;     // CRTP - trivial
  Extract<Derived, int>::type;    // type == int, Derived is derived from Base<int>
  Extract<Derived, double>::type; // compile-time error, undefined template

  return 0;
}

This isn't as convenient as passing some instance of a type to a deducing template function and have it magically , but you can at least test if some type T derives from Base<U> and get a compile-time error if it doesn't.

Upvotes: 2

BЈовић
BЈовић

Reputation: 64253

Since the base class has to be a concrete class (not a template), it is not possible to know whether it is a template or a non-template class.

In another words :

struct A1 : public B1
{};

struct A2 : public B2<int>
{};

in both of these cases both base classes are concrete types.

Upvotes: 0

Simple
Simple

Reputation: 14400

It's calling the const T& overload because its a better match than const base<T>&. The reason is because calling the first requires no conversions and the second requires a derived-to-base conversion.

Here's a quick hack that shows you how it can be done (note the introduced base class):

#include <iostream>
#include <type_traits>

struct EvenMoreBase {};

template <typename T>
struct Base : EvenMoreBase
{
};

struct Derived
:
    public Base<int>
{
};

template <typename T>
typename std::enable_if<!std::is_base_of<EvenMoreBase, T>::value>::type
Foo(const T&)
{
    std::cout << "generic" << std::endl;
}

template <typename T>
void Foo(const Base<T>&)
{
    std::cout << "derives from Base<T>" << std::endl;
}

template <typename T>
void Caller(const T& t)
{
    Foo(t);
}

int main()
{
    double x;
    Caller(x);

    Derived d;
    Caller(d);

    return 0;
}

Upvotes: 2

Related Questions