目的
当我们要根据不同机器使用不同的超参数时候,可以使用下面模板编程实现
base
英伟达有很多显卡,显卡架构各有不同,但是如果写一个kernel给英伟达公司,就需要针对每一个架构写一套函数,如下:
kernel_35();
kernel_52();
kernel_75();
kernel_80();
但是很多时候英伟达各个架构显卡基础逻辑都兼容,但是为了不同的性能会配置不同的超参数:
struct Arch_35
{
static constexpr int M = 35;
static constexpr int N = 35;
}
struct Arch_52
{
static constexpr int M = 52;
static constexpr int N = 52;
}
template<typename ARCH>
kernel()
{
int res = ARCH::M * ARCH::N;
};
// 正常使用,根据机器型号k选择对应的配置参数。
kernel<Arch_k>
你一个框架总不能让用户自己去指定吧(白嫖党要求真高),所以用户应该是无感知,使用方式如下:
kernel<ARCH_auto>();
如下实现上述效果:
#include <iostream>
using namespace std;
constexpr int MACHINE = 80;
template<int VERSION, typename Policy, typename PrePolicy>
struct ChainedPolicy
{
using ActivePolicy = typename std::conditional<(VERSION>MACHINE), typename PrePolicy::ActivePolicy, Policy>::type;
};
template<int VERSION, typename Policy>
struct ChainedPolicy<VERSION, Policy, Policy>
{
using ActivePolicy = Policy;
};
struct Policy50 : ChainedPolicy<50, Policy50, Policy50>
{
static constexpr int M = 50;
static constexpr int N = 50;
};
struct Policy70 : ChainedPolicy<70, Policy70, Policy50>
{
static constexpr int M = 70;
static constexpr int N = 70;
};
struct Policy80 : ChainedPolicy<80, Policy80, Policy70>
{
static constexpr int M = 80;
static constexpr int N = 80;
};
struct Policy85 : ChainedPolicy<85, Policy85, Policy80>
{
static constexpr int M = 85;
static constexpr int N = 85;
};
struct Policy90 : ChainedPolicy<90, Policy90, Policy85>
{
static constexpr int M = 90;
static constexpr int N = 90;
};
using Arch = Policy90;
int main()
{
Arch::ActivePolicy a;
std::cout<<a.M<<std::endl;
}
上面的设计可以达到目的,假如机器是80的arch,那么最终生效的就是Policy80, 好了目前为止又掌握了一个模板炫技的戏法。
奇异递归模板
上面是一个很好的例子,这个例子的本质就是奇异递归模板,下面好好研究一下这一个编程技术,原理是啥,使用场景是啥?
CRTP(Curiously Recurring Template Pattern)是一种使用C++模板元编程技术的设计模式,用于实现静态多态性。它是通过在派生类中继承基类,并将派生类自身作为基类的模板参数来实现的。具体而言,CRTP允许派生类在编译时通过继承和重载来覆盖基类的行为,而不需要运行时的虚函数调用。
常见的使用方式是:
template <typename D> class Base{/*do something*/};
class Derived : public Base<Derived> {/*do something*/};
这个看起来就是很奇怪,一个类型里面包含了一个继承于自己的类,这个话说起来就很费劲,所以一旦用这个编程技巧,可读性也是直线下降,常用在某些函数无法声明为虚函数的时候,下面是个小栗子:
template <typename D>
struct Base
{
template<typename T>
void Func(T& input) {
D* ptr = static_cast<D*>(this);//注意这里用static_cast,其实也不安全,自己把握,我感觉个reinterpret_cast没啥区别
ptr->Imp(input);
}
};
struct Derive: public Base<Derive>
{
template<typename T>
void Imp(T& input){}
}
Derive d;
d.Func(in);