我在C ++中阅读了Wikipedia article有关奇怪的重复出现的模板模式,用于执行静态(读取:编译时)多态。我想概括它,以便我可以根据派生类型更改函数的返回类型。 (这似乎应该是可能的,因为基类型知道模板参数中的派生类型)。不幸的是,以下代码将无法使用MSVC 2010进行编译(我现在没有轻松访问gcc所以我还没有尝试过)。谁知道为什么?
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;
}
顺便说一句,我有一个使用额外模板参数的解决方法,但我不喜欢它 - 当在继承链上传递许多类型时它会变得非常冗长。
template <typename derived_t, typename value_type>
class base { ... };
template <typename T>
class derived : public base<derived<T>,T> { ... };
修改
MSVC 2010在这种情况下提供的错误消息是error C2039: 'value_type' : is not a member of 'derived<T>'
g ++ 4.1.2(通过codepad.org)说error: no type named 'value_type' in 'class derived<int>'
答案 0 :(得分:56)
derived
用作base
的模板参数时, // 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();
}
};
不完整。
常见的解决方法是使用traits类模板。这是你的例子,traitsified。这显示了如何通过特征使用派生类中的类型和函数。
base_traits
您只需要将derived_t
专门用于base
的模板参数base
所使用的任何类型,并确保每个专门化都提供{{1}的所有成员需要。
答案 1 :(得分:8)
使用traits的一个小缺点是你必须为每个派生类声明一个。您可以编写一个不那么冗长和重新划分的解决方法,如下所示:
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;
}
答案 2 :(得分:6)
在C ++ 14中,您可以删除typedef
并使用函数auto
返回类型扣除:
template <typename derived_t>
class base {
public:
auto foo() {
return static_cast<derived_t*>(this)->foo();
}
};
这是有效的,因为base::foo
的返回类型的推断会延迟到derived_t
完成。
答案 3 :(得分:2)
需要较少样板文件的类型特征的替代方法是将派生类嵌套在包含typedef(或使用)的包装类中,并将包装器作为模板参数传递给基类。
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);
}
答案 4 :(得分:0)
我知道这基本上是您发现并且不喜欢的解决方法,但是我想对其进行记录,并说这基本上是该问题的当前解决方案。
一段时间以来,我一直在寻找一种实现此目的的方法,但从未找到好的解决方案。
不可能的事实是最终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;}
如果可能的话,可以将派生类的所有特征隐式传递给基类。我发现获得相同效果的成语是将特征完全传递给基类。
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;}
缺点是派生类中的特征必须由合格的typename
访问或由using
重新启用访问。