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

Допустим, у меня есть 2 функции, которые выполняют одинаковые операции над аргументами, но используют для этого разные наборы констант. Для упрощенного примера:

int foo1(int x){
    return 3+4*x
}
int foo2(int x){
    return 6-4*x
}

В реальных приложениях предполагается, что будет несколько аргументов и констант / литералов, и, конечно, вычисления будут намного более сложными. Для простоты, а также для удобства обслуживания, я хочу переписать эти две функции как шаблон, который может производить обе эти функции, чтобы я мог вызвать foo<1> или foo<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;
}

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

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

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);
}
Другие вопросы по тегам