Функция со switch с переменным количеством case

321
16 января 2017, 20:37

Допустим, у меня есть несколько таких блоков:

while (true) {
    // Ввод символа
    do switch (_getch()) {
    // Делать одно
    case '1': dosmth1();
        break;
    // Делать второе
    case '2': dosmth2();
        break;
    // Делать третье
    case '3': dosmth3();
        break;
    // Выйти
    case '0': dosmth0();
        return 0;
    // Повторить ввод
    default: continue;
    } while (false);
}

Но, ввиду частого создания оных, возникает вопрос:

Как организовать такую функцию menu(), которая создавала бы n-ное количество case, соответствующих n-ному количеству параметров (указателей на функции)?

Вызов такой функции по принципу stdarg:

menu(4, dosmth1, dosmth2, dosmth3, dosmth0);
Answer 1

Если сигнатуры вызываемых функций одинаковы, то самый простой способ - использовать контейнер типа std::map, std::unordered_map. Ключом будет выступать char или int, а в качестве типа значения использоваться указатель на функцию. Например:

std::map<int, void(*)()> action;

Когда контейнер заполнен использование может быть сокращено до вида:

action[_getch()]();

Понятно, что в общем случае могут потребоваться дополнительные проверки на существование элемента, т.к. по умолчанию оператор [] приводит к созданию элемента, если его не было.

Answer 2

Несмотря на то что ответ уже принят, предложу вариант решения с помощью шаблонов с переменным числом аргументов (работает начиная с C++11):

typedef void(*ActionFunction)();
bool _menu(int condition, int action_counter, ActionFunction action)
{
    if (action_counter == condition) {
        action();
        return true;
    } else {
        return false;
    }
}
template<typename... Args>
bool _menu(int condition, int action_counter, ActionFunction action, Args... args)
{
    if (action_counter == condition) {
        action();
        return true;
    }
    return _menu(condition, ++action_counter, args...);
}
template<typename... Args>
bool menu(int condition, ActionFunction action, Args... args)
{
    return _menu(condition, 0, action, args...);
}

Функция menu принимает номер функции, которую надо выполнить (действия нумеруются с нуля), и дальше любое количество указателей на функции. Если функции будут иметь разную сигнатуру, то надо просто дополнительно определить пару функций _menu (шаблонную и обычную) с нужным типом аргумента в третьей позиции. Также функция menu возвращает true, если значение было найдено, и false, если нет.

Пример (функции имеют сигнатуру void(*)()):

#include <stdio.h>
void print0() { printf("0\n"); }
void print1() { printf("1\n"); }
void print2() { printf("2\n"); }
void print3() { printf("3\n"); }
typedef void(*ActionFunction)();
bool _menu(int condition, int action_counter, ActionFunction action)
{
    if (action_counter == condition) {
        action();
        return true;
    } else {
        return false;
    }
}
template<typename... Args>
bool _menu(int condition, int action_counter, ActionFunction action, Args... args)
{
    if (action_counter == condition) {
        action();
        return true;
    }
    return _menu(condition, ++action_counter, args...);
}
template<typename... Args>
bool menu(int condition, ActionFunction action, Args... args)
{
    return _menu(condition, 0, action, args...);
}
int main() {
    menu(1, print0, print1, print2, print3); // вывод будет 1
    menu('2' - 48, print0, print1, print2, print3); // вывод будет 2, так как в
    // ascii символы цифр расположены начиная с 48 позиции (48 символ ascii это '0')
    return 0;
}

Еще пример на ideone.

Как это работает: сначала вызывается menu с аргументами, описанными выше. Далее она вызывает _menu, передавая ей свои аргументы + устанавливая счетчик в 0. Функция _menu условно "откусывает" одну функцию action от всех остальных, оставляя остальные в args, потом смотрит, совпадает ли счетчик с условием, и, если да, выполняет action и завершается, возвращая true. Если условие не равно счетчику, то она увеличивает его и рекурсивно вызывает себя, передавая условие, увеличенный счетчик и оставшиеся функции. Если функция осталась одна, то вызывается обычная, а не шаблонная версия функции, и она возвращает false, если условие и счетчик не равны.

Answer 3

Можно примерно вот таким подходом (пример на ideone):

#include <iostream>
#include <vector>
typedef void (*FuncType)();
typedef std::vector<FuncType> SwitchType;
void FuncOne() {
  std::cout << "One" << std::endl;    
}
void FuncTwo() {
  std::cout << "Two" << std::endl;    
}
int main() {
  SwitchType Switch;
  Switch.push_back(FuncOne);  
  Switch.push_back(FuncTwo);  
  Switch.push_back(FuncOne);
  for(const auto i: {0,1,2}) Switch[i]();  
  return 0;    
}

Вывод:

One
Two
One

Основная идея - заменить конструкцию switch на вектор указателей на функции. Приведенный выше пример конечно незаконченный (default секции нет), индексы строго по порядку. Но это все легко решается. К примеру, вектор можно заменить std::map, а секцию default обработать пробросом исключения.

READ ALSO
ImageMagick C++ API бинарное (1 bit per pixel) изображение

ImageMagick C++ API бинарное (1 bit per pixel) изображение

Существует ли возможность создания 1-битных изображений и конвертации в этот формат изображений иных форматов с помощью библиотеки imagemagick?...

314
Движение игрока в Box2D после прыжка

Движение игрока в Box2D после прыжка

В игре присутствует модель игрокаСуть в том, что при нажатии W игрок просто летит вверх

323
Имеется ли число в enum

Имеется ли число в enum

Программа читает число с flash-памяти внешнего устройства (целое, 1 байт, беззнаковое)Для его программной интерпретации используется перечисление:

352
Указатели, следующее значение

Указатели, следующее значение

Вывод значений массива, получить 4 элемента по очереди, результат

264