Допустим, у меня есть 2 функции, которые выполняют одинаковые операции над аргументами, но используют для этого разные наборы констант. Для упрощенного примера:
int foo1(int x){
return 3+4*x
}
int foo2(int x){
return 6-4*x
}
В реальных приложениях предполагается, что будет несколько аргументов и констант / литералов, и, конечно, вычисления будут намного более сложными.
Для простоты, а также для удобства обслуживания я хочу переписать эти две функции как шаблон, который может производить обе эти функции, чтобы я мог вызвать foo<1> или фу<2> и правильная функция будет сгенерирована. Я знаю, что я мог бы сделать что-то вроде этого:
int foo(int x, int funcType){
const int firstConst = (funcType==1) ? 3 : 6;
const int secondConst = (funcType==1) ? 4 : -4;
return firstConst+secondConst*x;
}
но так как я всегда знаю во время компиляции, какую функцию я хочу использовать, я хотел бы использовать шаблоны, чтобы избежать ветвления. Есть какой-либо способ сделать это?
templatr<int funcType>
void foo(int x){
const int firstConst = (funcType==1) ? 3 : 6;
const int secondConst = (funcType==1) ? 4 : -4;
return firstConst+secondConst*x;
}
ни один компилятор, который стоит использовать при любой ненулевой настройке оптимизации, не будет иметь ветки времени выполнения для вышеуказанной функции шаблона.
И это часто легче читать, чем черты классов.
В общем, вы можете получить удовольствие от этой техники, написав длинный ветвящийся код, который компилируется в сложные операции. Это достаточно хорошо масштабируется, если ваш код хорошо разлагается на части (например, bool do_foo
в качестве параметра шаблона).
За пределами этого масштабирования вы, вероятно, хотите избежать ведения центрального списка числовых идентификаторов. Поиск признаков путем отправки тега в функцию признаков constexpr с поддержкой ADL или указатель на тип не указателя типа на структуру constexpr, оба могут дать вам эффект нулевых издержек с объявлением подтипа распределенной функции.
Наконец, вы можете просто передать класс черты напрямую:
template<class Traits>
void foo(int x){
return x*Traits::z+Traits::y;
}
или же
template<class Traits>
void foo(int x){
return x*Traits{}.z+Traits{}.y;
}
или даже
template<class Traits>
void foo(int x, Traits traits={}){
return x*traits.z+traits.y;
}
в зависимости от конкретных потребностей.
Вы можете использовать шаблон класса признаков для отдельного управления константами / литералами, например,
template <int FuncType>
struct Constants;
template <>
struct Constants<1> {
static const int firstConst = 3;
static const int secondConst = 4;
};
template <>
struct Constants<2> {
static const int firstConst = 6;
static const int secondConst = -4;
};
template <int FuncType>
int foo(int x){
return Constants<FuncType>::firstConst + Constants<FuncType>::secondConst * x;
}
затем назовите это как
foo<1>(42);
foo<2>(42);
Как и в случае с чертами, вы можете сделать:
template <int a, int b>
int foo(int x)
{
return a * x + b;
}
int foo1(int x){
return foo<4, 3>(x);
}
int foo2(int x){
return foo<-4, 6>(x);
}