Derived class template CRTP
CRTP是基于inheritance的,一般我们见到的CRTP的用法中,derived class都是class,那derived class template呢?它能否用于CRTP呢?在使用它的过程中,是否会存在一些问题?本章对这个topic进行讨论。
Derived class template CRTP
C++是允许的,下面是example。
Example 我的实践
#include <iostream>
template<typename UstTag, typename SpiType>
class CUstApi
{
protected:
template<typename ServiceImpl, typename ServiceTrait>
struct CServiceRspBase
{
public:
CServiceRspBase()
{
}
int Run()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
// 回调
return static_cast<ServiceImpl*>(this)->CallBack();
}
};
};
template<typename UstTag, typename SpiType>
class CUstApiImpl: public CUstApi<UstTag, SpiType>
{
protected:
template<typename ServiceTrait>
struct CServiceMultiSpanRsp: CUstApi<UstTag, SpiType>::template CServiceRspBase<CServiceMultiSpanRsp<ServiceTrait>, ServiceTrait>
{
using Base = typename CUstApi<UstTag, SpiType>:: template CServiceRspBase<CServiceMultiSpanRsp<ServiceTrait>, ServiceTrait>;
friend Base;
protected:
int CallBack()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
return 0;
}
};
public:
class CTestServiceTrait
{
};
void Test()
{
CServiceMultiSpanRsp<CTestServiceTrait> s;
s.Run();
}
};
struct TestUstTag
{
};
struct TestSpiType
{
};
int main()
{
CUstApiImpl<TestUstTag, TestSpiType> Api;
Api.Test();
}
// g++ test.cpp -Wall -pedantic
输出如下:
int CUstApi<UstTag, SpiType>::CServiceRspBase<ServiceImpl, ServiceTrait>::Run() [with ServiceImpl = CUstApiImpl<TestUstTag, TestSpiType>::CServiceMultiSpanRsp<CUstApiImpl<TestUstTag, TestSpiType>::CTestServiceTrait>; ServiceTrait = CUstApiImpl<TestUstTag, TestSpiType>::CTestServiceTrait; UstTag = TestUstTag; SpiType = TestSpiType]
int CUstApiImpl<UstTag, SpiType>::CServiceMultiSpanRsp<ServiceTrait>::CallBack() [with ServiceTrait = CUstApiImpl<TestUstTag, TestSpiType>::CTestServiceTrait; UstTag = TestUstTag; SpiType = TestSpiType]
Base access member in derived
stackoverflow 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.
#include <iostream>
template<typename derived_t>
class base
{
public:
typedef typename derived_t::value_type value_type;
value_type foo()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
return static_cast<derived_t*>(this)->foo();
}
};
template<typename T>
class derived: public base<derived<T> >
{
public:
typedef T value_type;
value_type foo()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
return T(); //return some T object (assumes T is default constructable)
}
};
int main()
{
derived<int> a;
}
// g++ test.cpp -Wall -pedantic
NOTE:
1、编译报错如下:
test.cpp: In instantiation of ‘class base<derived<int> >’: test.cpp:16:7: required from ‘class derived<int>’ test.cpp:29:15: required from here test.cpp:7:41: 错误:no type named ‘value_type’ in ‘class derived<int>’ typedef typename derived_t::value_type value_type; ^ test.cpp: 在函数‘int main()’中: test.cpp:29:15: 警告:未使用的变量‘a’ [-Wunused-variable] derived<int> a;
2、上述程序是想实现CRTP static polymorphism,但是它的写法是有问题的,参见下面的"完整测试程序"获得正确的写法
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> { ... };
NOTE:
1、完整测试程序如下:
#include <iostream> template<typename derived_t, typename value_type> class base { public: value_type foo() { std::cout << __PRETTY_FUNCTION__ << std::endl; return static_cast<derived_t*>(this)->foo_impl(); } }; template<typename T> class derived: public base<derived<T>, T> { friend class base<derived<T>, T> ; protected: typedef T value_type; value_type foo_impl() { std::cout << __PRETTY_FUNCTION__ << std::endl; return T(); //return some T object (assumes T is default constructable) } }; int main() { derived<int> a; a.foo(); } // g++ test.cpp -Wall -pedantic
输出如下:
value_type base<derived_t, value_type>::foo() [with derived_t = derived<int>; value_type = int] derived<T>::value_type derived<T>::foo_impl() [with T = int; derived<T>::value_type = int]
A
derived
is incomplete when you use it as a template argument to base
in its base classes list.
A common workaround is to use a traits class template. Here's your example, traitsified. This shows how you can use both types and functions from the derived class through the traits.
#include <iostream>
// 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()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
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()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
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();
}
};
int main()
{
derived<int> d;
d.base_foo();
}
// g++ test.cpp -Wall -pedantic
NOTE:
1、这种实现方式是非常冗杂的
2、输出如下:
base<derived_t>::value_type base<derived_t>::base_foo() [with derived_t = derived<int>; base<derived_t>::value_type = int] derived<T>::value_type derived<T>::derived_foo() [with T = int; derived<T>::value_type = int]
You just need to specialize base_traits
for any types that you use for the template argument derived_t
of base
and make sure that each specialization provides all of the members that base
requires.
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();
}
};
This works because the deduction of the return type of base::foo
is delayed until derived_t
is complete.
NOTE:
1、完整的测试程序如下:
#include <iostream> template<typename derived_t> class base { public: auto foo() { std::cout << __PRETTY_FUNCTION__ << std::endl; return static_cast<derived_t*>(this)->foo_impl(); } }; template<typename T> class derived: public base<derived<T>> { friend class base<derived<T>> ; protected: typedef T value_type; value_type foo_impl() { std::cout << __PRETTY_FUNCTION__ << std::endl; return T(); //return some T object (assumes T is default constructable) } }; int main() { derived<int> a; a.foo(); } // g++ --std=c++14 test.cpp -Wall -pedantic
输出如下:
auto base<derived_t>::foo() [with derived_t = derived<int>] derived<T>::value_type derived<T>::foo_impl() [with T = int; derived<T>::value_type = int]
A
One small drawback of using traits is that you have to declare one for each derived class. You can write a less verbose and redondant workaround like this :
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;
}