C++ static polymorphism (CRTP) and using typedefs from derived classes

I read the Wikipedia article about the curiously recurring template pattern in C++ for doing static (read: compile-time) polymorphism. I wanted to generalize it so that I could change the return types of the functions based on the derived type. (This seems like it should be possible since the base type knows the derived type from the template parameter). Unfortunately, the following code won't compile using MSVC 2010 (I don't have easy access to gcc right now so I haven't tried it yet). Anyone know why?

template <typename derived_t>
class base {
public:
typedef typename derived_t::value_type value_type;
value_type foo() {
return static_cast<derived_t*>(this)->foo();
}
};


template <typename T>
class derived : public base<derived<T> > {
public:
typedef T value_type;
value_type foo() {
return T(); //return some T object (assumes T is default constructable)
}
};


int main() {
derived<int> a;
}

BTW, I have a work-around using extra template parameters, but I don't like it---it will get very verbose when passing many types up the inheritance chain.

template <typename derived_t, typename value_type>
class base { ... };


template <typename T>
class derived : public base<derived<T>,T> { ... };

EDIT:

The error message that MSVC 2010 gives in this situation is error C2039: 'value_type' : is not a member of 'derived<T>'

g++ 4.1.2 (via codepad.org) says error: no type named 'value_type' in 'class derived<int>'

15462 次浏览

如果将 derived用作基类列表中 base的模板参数,则 derived不完整。

一种常见的解决方案是使用 trait 类模板。这是你的例子,背叛。这展示了如何通过 trait 使用派生类中的类型和函数。

// Declare a base_traits traits class template:
template <typename derived_t>
struct base_traits;


// Define the base class that uses the traits:
template <typename derived_t>
struct base {
typedef typename base_traits<derived_t>::value_type value_type;
value_type base_foo() {
return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
}
};


// Define the derived class; it can use the traits too:
template <typename T>
struct derived : base<derived<T> > {
typedef typename base_traits<derived>::value_type value_type;


value_type derived_foo() {
return value_type();
}
};


// Declare and define a base_traits specialization for derived:
template <typename T>
struct base_traits<derived<T> > {
typedef T value_type;


static value_type call_foo(derived<T>* x) {
return x->derived_foo();
}
};

您只需要为 base的模板参数 derived_t使用的任何类型专门化 base_traits,并确保每个专门化都提供了 base所需的所有成员。

使用 trait 的一个小缺点是必须为每个派生类声明一个。你可以像下面这样写一个不那么冗长和冗余的解决方案:

template <template <typename> class Derived, typename T>
class base {
public:
typedef T value_type;
value_type foo() {
return static_cast<Derived<T>*>(this)->foo();
}
};


template <typename T>
class Derived : public base<Derived, T> {
public:
typedef T value_type;
value_type foo() {
return T(); //return some T object (assumes T is default constructable)
}
};


int main() {
Derived<int> a;
}

In C++14 you could remove the typedef and use function auto return type deduction:

template <typename derived_t>
class base {
public:
auto foo() {
return static_cast<derived_t*>(this)->foo();
}
};

这是因为 base::foo返回类型的推导被推迟到 derived_t完成。

需要较少样板的类型 trait 的一种替代方法是将派生类嵌套在一个包装类中,该包装类保存 typedefs (或使用的) ,并将包装作为模板参数传递给基类。

template <typename Outer>
struct base {
using derived = typename Outer::derived;
using value_type = typename Outer::value_type;
value_type base_func(int x) {
return static_cast<derived *>(this)->derived_func(x);
}
};


// outer holds our typedefs, derived does the rest
template <typename T>
struct outer {
using value_type = T;
struct derived : public base<outer> { // outer is now complete
value_type derived_func(int x) { return 5 * x; }
};
};


// If you want you can give it a better name
template <typename T>
using NicerName = typename outer<T>::derived;


int main() {
NicerName<long long> obj;
return obj.base_func(5);
}

我知道这基本上就是你们找到的解决方案,但我想记录下来,也想说这基本上就是目前解决这个问题的方案。

我一直在寻找一个方法来做这一段时间,从来没有找到一个好的解决方案。 事实上这是不可能的,这就是为什么像 boost::iterator_facade<Self, different_type, value_type, ...>这样的东西最终需要很多参数的原因。

当然,我们希望这样的事情能够奏效:

template<class CRTP>
struct incrementable{
void operator++(){static_cast<CRTP&>(*this).increment();}
using ptr_type = typename CRTP::value_type*; // doesn't work, A is incomplete
};


template<class T>
struct A : incrementable<A<T>>{
void increment(){}
using value_type = T;
value_type f() const{return value_type{};}
};


int main(){A<double> a; ++a;}

如果可能的话,派生类的所有 trait 都可以隐式地传递给基类。我发现获得同样效果的习惯做法是将 trait 完全传递给基类。

template<class CRTP, class ValueType>
struct incrementable{
void operator++(){static_cast<CRTP&>(*this).increment();}
using value_type = ValueType;
using ptr_type = value_type*;
};


template<class T>
struct A : incrementable<A<T>, T>{
void increment(){}
typename A::value_type f() const{return typename A::value_type{};}
//    using value_type = typename A::value_type;
//    value_type f() const{return value_type{};}
};


int main(){A<double> a; ++a;}

Https://godbolt.org/z/2g4w7d

缺点是派生类中的 trait 必须通过 using的限定 typename重新启动来访问。