是否有可能在编译时检查类型是否来自模板的某些实例化?

时间:2013-09-26 07:24:10

标签: c++ inheritance metaprogramming

我想编写一个模板函数,如果传递的类型是从另一个类的任何模板实例化派生的,那么它会表现一种方式,如果不是,则表示另一种方式。

我认为下面的代码捕获了我想要做的事情。不幸的是,来电者为doubleDerived打印“通用”。

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

(请注意,调用者不知道其参数可能派生自哪个Base实例。)

3 个答案:

答案 0 :(得分:2)

它调用const T&重载,因为它比const base<T>&更好。原因是因为调用第一个不需要转换,第二个需要派生到基础的转换。

这是一个快速入侵,向您展示如何完成它(请注意引入的基类):

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

答案 1 :(得分:2)

如果您能够使用C ++ 11(或一般<type_traits>),以下也是一种可能的解决方案,不仅涵盖T : Base<T>类型,即CRTP的实例,还有T : Base<U>没有其他基类,如您的示例所示。

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

请注意typedef T base_value_type - 可能会被调用。我们的想法是,从T派生的每个类型Base<U>都可以利用基础模板参数的知识。是否T == U无关紧要。传入没有T的{​​{1}}时,尝试替换第二个参数将失败,因此不会生成此特定typedef T base_value_type的特化。

编辑:处理完您的评论后,受到我发布的帖子的启发,我在检查某些时间类型T时尝试以某种方式提取一些基本参数U。我不认为这可以按照你想要的方式完成,即你通过任何T : Base<U>并提取T。但是,你可以做两件事。

简单解决方案:如果您可以控制如何实现派生类,而不是在基类中添加U,只需在派生类中添加相应的typedef:

typedef

或者,如果您不希望派生类也是类模板,只需将类型硬编码到类型中(您已经知道基本参数的类型):

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

更多涉及的解决方案:您可以执行的操作,至少对于可能class Derived : public Base<int> { public: typedef int base_param_type; } 预期子集,您可以执行以下操作:

U

这不像将一个类型的实例传递给演绎模板函数并且神奇地拥有它那样方便,但你至少可以测试某些类型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; } 是否来自T并得到一个编译时错误,如果没有。

答案 2 :(得分:0)

由于基类必须是具体类(不是模板),因此无法知道它是模板还是非模板类。

换句话说:

struct A1 : public B1
{};

struct A2 : public B2<int>
{};

在这两种情况下,两个基类都是具体的类型。