Шаблоны C ++, чтобы избежать длинных переключений при вызове функции с разными типами возврата - PullRequest
0 голосов
/ 10 июля 2020

У меня много функций q1, q2, q3, et c., Каждая из которых имеет свой тип возвращаемого значения (int, int64_t, std::string, et c.) .

У меня также есть функция print_result, которая выводит их результаты (и время, необходимое для их выполнения, но здесь обрезано для простоты):

template <typename T>
void print_result(T (*func)()) {
  T res = func();
  std::cout << res << std::endl;
}

У меня также есть большой переключатель оператор для печати результата для каждой из функций:

switch (question_num) {
  case 1: print_result(q1); break;
  case 2: print_result(q2); break;
  case 3: print_result(q3); break;
  // ...
}

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

Я попытался взглянуть на Создание экземпляра шаблона C ++: избегая длинных переключений , но я новичок в метапрограммировании шаблонов, поэтому не знаю, как именно с этим справиться.

Моя текущая попытка, которая не компилируется:


template <<int, typename> ...> struct FuncList {};

template <typename T>
bool handle_cases(int, T, FuncList<>) {
  // default case
  return false;
}

template <<int I, typename T> ...S>
bool handle_cases(int i, T (*func)(), FuncList<T, S...>) {
  if (I != i) {
    return handle_cases(i, func, FuncList<S...>());
  }
  print_result(func);
  return true;
}

template <typename ...S>
bool handle_cases(int i, T (*func)()) {
  return handle_cases(i, func, FuncList<S...>());
}

// ...
  bool res = handle_cases<
    <1, q1>, <2, q2>, <3, q3>
  >(question_num);
// ...

Мой идеальный способ использования этого шаблона показан в последней строке.

Обратите внимание, что сопоставления из функции там указан номер функции. Номера функций фиксированы, т.е. q1 сопоставляется с константой 1, и это не изменится во время выполнения.

Ошибка компиляции (это может быть скорее базовая c, но я действительно не много знаю о метапрограммировании):

error: expected unqualified-id before ‘<<’ token
   17 | template <<int, typename> ...> struct FuncList {};
      |          ^~

Ответы [ 4 ]

1 голос
/ 10 июля 2020

Учитывая вашу «текущую попытку» ... мне кажется, что вы могли бы написать handle_cases структуру / класс почти следующим образом:

struct handle_cases
 {
   std::map<int, std::function<void()>> m;

   template <typename ... F>
   handle_cases (std::pair<int, F> const & ... p)
      : m{ {p.first, [=]{ print_result(p.second); } } ... }
    { }

   void operator() (int i)
    { m[i](); }
 };

с картой между целым числом и лямбдой, которая вызывает print_result с функцией и operator(), которые вызывают запрошенную лямбду с учетом соответствующего индекса.

Вы можете создать объект класса следующим образом (к сожалению, я не вижу способа избежать std::make_pair() s)

handle_cases hc{ std::make_pair(10, q1),
                 std::make_pair(20, q2),
                 std::make_pair(30, q3),
                 std::make_pair(40, q4) };

и используя его следующим образом

hc(30);

Ниже приведен пример полной компиляции

#include <functional>
#include <map>
#include <iostream>

template <typename T>
void print_result (T(*func)())
 {
   T res = func();
   std::cout << res << std::endl;
 }

struct handle_cases
 {
   std::map<int, std::function<void()>> m;

   template <typename ... F>
   handle_cases (std::pair<int, F> const & ... p)
      : m{ {p.first, [=]{ print_result(p.second); } } ... }
    { }

   void operator() (int i)
    { m[i](); }
 };

char      q1 () { return '1'; }
int       q2 () { return 2; }
long      q3 () { return 3l; }
long long q4 () { return 4ll; }

int main ()
 {
   handle_cases hc{ std::make_pair(10, q1),
                    std::make_pair(20, q2),
                    std::make_pair(30, q3),
                    std::make_pair(40, q4) };

   hc(30);
 }
1 голос
/ 10 июля 2020

Вам может понравиться версия, которая не требует каких-либо контейнеров времени выполнения, не генерирует никаких промежуточных объектов и даже не генерирует таблицу данных и генерирует очень меньше кода, а также проста в использовании: *

1 голос
/ 10 июля 2020

Если вы умеете использовать C ++ 17, вот «упрощенная» версия подхода @Klaus. Вместо использования заранее созданной рекурсивной структуры вы можете использовать кратное выражение c ++ 17:

template<auto... Funcs, std::size_t... I>
bool select_case(std::size_t i, std::integer_sequence<std::size_t, I...>) {
    return ([&]{ if(i == I) { print_result(Funcs); return true; } return false; }() || ... ); 
}

template<auto... Funcs>
struct FuncSwitch {

    static bool Call(std::size_t i) {
        return select_case<Funcs...>(i, std::make_index_sequence<sizeof...(Funcs)>());
    }
};

Идея состоит в том, чтобы обернуть каждый из Funcs в лямбду, чтобы только функция, соответствующая переданный индекс называется. Обратите внимание, что || в выражении кратного короткого замыкания. Будет использоваться так:

float q0() { return 0.f; }
int q1() { return 1; }
std::string q2() { return "two"; }


int main() {

    bool success = FuncSwitch<q0, q1, q2>::Call(1);
}

См. здесь для полного примера.

1 голос
/ 10 июля 2020

У меня другое предложение:

  1. Используйте std :: array вместо switch (или std :: map, если случаи переключения не непрерывны, std :: array имеет O (1) время доступа, std :: map O (log (n)) и переключатель O (n).
  2. Используйте std :: function и std :: bind для привязки ваших функций, которые вы хотите вызвать, к объект функтора
  3. использовать индекс в массиве для вызова функции
  4. Используйте заполнители, если вам нужно передать дополнительные данные
#include <iostream>
#include <functional>

template <typename T>
void print_result(T (*func)()) {
  T res = func();
  std::cout << res << std::endl;
}

int int_function() {
    return 3;
}

double double_function() {
    return 3.5;
}

std::array<std::function<void()>, 2> functions({
    std::bind(print_result<int>, int_function),
    std::bind(print_result<double>, double_function),
});

int main() {

    functions[0]();
    functions[1]();

    return 0;
}

Вывод:

3
3.5

См .: Почему std :: function может неявно преобразовывать в std :: function с дополнительным параметром?

Обновление:

С передачей параметров:

#include <iostream>
#include <functional>

template <typename T>
void print_result(T (*func)(int), int value) {
  T res = func(value);
  std::cout << res << std::endl;
}

int int_function(int value) {
    return 3 * value;
}

double double_function(int value) {
    return 3.5 * value;
}

std::array<std::function<void(int)>, 2> functions({
    std::bind(print_result<int>, int_function, std::placeholders::_1),
    std::bind(print_result<double>, double_function, std::placeholders::_1),
});

int main() {

    functions[0](10);
    functions[1](11);

    return 0;
}

Вывод:

30
38.5
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...