Использование шаблонов, чтобы избежать подобных функций

Допустим, у меня есть 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;
}

но так как я всегда знаю во время компиляции, какую функцию я хочу использовать, я хотел бы использовать шаблоны, чтобы избежать ветвления. Есть какой-либо способ сделать это?

3

Решение

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;
}

в зависимости от конкретных потребностей.

3

Другие решения

Вы можете использовать шаблон класса признаков для отдельного управления константами / литералами, например,

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);
3

Как и в случае с чертами, вы можете сделать:

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);
}
1