Подтвердить что ты не робот

Создание экземпляра шаблона С++: исключение длинных переключателей

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

#include <string>
#include <iostream>
#include <type_traits>

template<unsigned A>
struct Wrapper {
    typedef typename std::conditional<A==1, int, float>::type DataType;
    DataType content[A];
    void foo() {
        std::cout << A << std::endl;
    };
};    

int main(int argc, char *argv[])
{
    std::string arg = argv[1];
    int arg_int = std::stoi(arg);

    switch (arg_int) {
    case 1: {
        Wrapper<1> w;
        w.foo();
        break;
    }
    case 2: {
        Wrapper<2> w;
        w.foo();
        break;
    }
    case 3: {
        Wrapper<3> w;
        w.foo();
        break;
    }
    default:
        return 1;
    };

    return 0;
}

Это быстро станет громоздким, если у меня есть не только один параметр A, но несколько аргументов шаблона в разных комбинациях. Предположим также, что на самом деле существует действительно хорошая причина для реализации A как параметра шаблона.

Есть ли способ заменить огромный оператор switch почти идентичными операциями case, например. используя некоторую магию метапрограммирования от Boost или взлома препроцессора?

В идеале я хотел бы написать что-то вроде следующего:

INSTANTIATE_DEPENDING(i, {1, 2, 3},
            {
                Wrapper<i> w;
                w.foo();
            }
    );
4b9b3361

Ответ 1

Вы можете использовать вариационный шаблон, возможно, так:

#include <cstdlib>
#include <string>

int main(int argc, char * argv[])
{
    if (argc != 2) { return EXIT_FAILURE; }

    handle_cases<1, 3, 4, 9, 11>(std::stoi(argv[1]));
}

Реализация:

template <int ...> struct IntList {};

void handle_cases(int, IntList<>) { /* "default case" */ }

template <int I, int ...N> void handle_cases(int i, IntList<I, N...>)
{
    if (I != i) { return handle_cases(i, IntList<N...>()); }

    Wrapper<I> w;
    w.foo();
}

template <int ...N> void handle_cases(int i)
{
    handle_cases(i, IntList<N...>());
}

Ответ 2

arg_int - это параметр времени выполнения, поэтому нет возможности привязать его непосредственно к параметру шаблона. Вы можете использовать какую-то таблицу обработчиков, которая могла бы удалить оператор switch здесь.

Вы использовали бы что-то вроде lookup_handler( int N ), возвращающее тип handler, который может быть лямбдой, вызывающей одну из этих функций шаблона.

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

template< unsigned N > register_lambda()
{
     table.add( Wrapper<N>() );
     register_lambda< N-1 >;
}

и специализируются на register_lambda<0>

Затем вы вызываете register_lambda<32>, и вы зарегистрировали все числа от 0 до 32.

Один из способов реализации такой таблицы:

class lambda_table
{
 typedef std::function<void()> lambda_type; 
    public:
        void add( lambda_type );
        bool lookup( size_t key, lambda_type & lambda ) const;
};

Из main() или там, где вы хотите его вызывать, у вас есть ссылка на эту таблицу (вызовите ее в таблице), затем вызовите

lambda_type lambda;
if( table.find( arg_int, lambda ) )
        lanbda();
else
      default_handler();

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

Хотя lambdas может обертывать все типы элементов данных, вы могли бы хотеть, чтобы ваши шаблоны были классами в иерархии, а не лямбдами, учитывая хранение данных в них.

Ответ 3

просто используйте макросы!

template<unsigned A>
struct Wrapper {
    int content[A];
    void foo() { };
};

#define WRAPPER_SWITCH_CASE(i) case i: Wrapper<i>().foo(); break;

int main(int argc, char *argv[])
{
    std::string arg = argv[1];
    int arg_int = std::stoi(arg);

    switch (arg_int) {
        WRAPPER_SWITCH_CASE(1)
        WRAPPER_SWITCH_CASE(2)
        WRAPPER_SWITCH_CASE(3)
        default: return 1;
    };

    return 0;
}

(живой пример)

Но, как вы знаете, макросы вредны; Я думаю, что Wrapper следует выделять content во время выполнения, а не в шаблон.

Ответ 4

краткое описание приложения концепции с использованием рекурсивного генератора для Wrappers:

#include <iostream>
#include <vector>

struct FooProvider
{
    virtual void foo() = 0;
};

template<unsigned A>
struct Wrapper : public FooProvider {
    Wrapper() {std::cout << A << std::endl;}
    int content[A];
    virtual void foo() { std::cout << "call:" << A << std::endl;};
};

static std::vector<FooProvider*> providers;

template <unsigned CTR>
struct Instantiator
{
    Instantiator()
    {
        providers.insert(providers.begin(), new Wrapper<CTR>);
        Instantiator<CTR - 1>();
    }
};

template <>
struct Instantiator<0>
{
    Instantiator() {}
};

int main()
{
    Instantiator<100>();
    providers[4]->foo();

    // do not forget to delete the providers
}

Ответ 5

Вы можете просто использовать макрос верхнего цикла, который передает реализацию блока в общий расширитель цикла:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B
#define M_ID(...) __VA_ARGS__

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
//...etc


#define INSTANTIATE_DEPENDING(L, C) M_FOR_EACH(C, M_ID L)

//...
#define CASE_BLOCK(n) case n: { Wrapper<n> w; w.foo(); break; }

INSTANTIATE_DEPENDING((1, 2, 3), CASE_BLOCK)

#undef CASE_BLOCK  //if you like, not essential to the concept

Не так много сказать об этом: цикл повторяет блок для длины переданного списка, передавая элементы в списке в макрос, который он должен развернуть. Таким образом, вы помещаете свою реализацию в этот макрос (и #undef, если хотите, чтобы он был локальным).

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

#include <order/interpreter.h>

ORDER_PP(    // runs Order code
  8for_each_in_range(8fn(8I,
                         8print( (case) 8I (: { )
                                    (Wrapper<) 8I (> w; w.foo(); break; }) )),
                     1, 4)
)

(Используйте 8for-each вместо 8for_each_in_range для несмежных списков. Заказ получил полную функциональную семантику программирования, поэтому такие вещи являются незначительными проблемами.)

Ответ 6

В качестве общей альтернативы коммутаторам вы можете использовать вектор или карту указателей функций для удаления коммутатора:

template <int i>
int foo()
{
    Wrapper<i> w;
    w.foo();
    return i;
}

static std::vector<int(*)()> m;

void init()
{
    m.push_back(&foo<0>);
    m.push_back(&foo<1>);
}

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

В С++ 11 вы можете использовать список инициализаторов для инициализации вектора или карты.

Ответ 7

Здесь другой подход:

template<int N>
void do_foo()
{
    Wrapper<N> w;
    w.foo();
}

template<int N, int... Ns>
struct fn_table : fn_table<N - 1, N - 1, Ns...>
{
};

template<int... Ns>
struct fn_table<0, Ns...>
{
    static constexpr void (*fns[])() = {do_foo<Ns>...};
};

template<int... Ns>
constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])();

int main(int argc, char *argv[])
{
    std::string arg = argv[1];
    int arg_int = std::stoi(arg);

    // 4 if you have Wrapper<0> to Wrapper<3>.
    fn_table<4>::fns[arg_int]();
}

Ответ 8

Вдохновленный ответ Kerrek SB с вариативными шаблонами, вот решение, которое можно легко расширить до нескольких параметров любого типа:

template <int param1_>
struct Params
{
    const static int kParam1 = param1_;
    // Add other parameters here if needed
};

// Default case: list is empty
template <typename T>
void handle_cases(const T param1) { }

// Regular case: list is not-empty
template <typename T, typename head, typename ...tail>
void handle_cases(const T param1)
{
    if (head::kParam1 == param1)
    {
        Wrapper<head::kParam1> w;
        w.foo();
    }
    else
    {
        handle_cases<T, tail...>(param1);
    }
}

Обратите внимание, что typename T является просто примером дополнительного параметра шаблона, который не является частью списка head/tail.

И вот как его использовать:

int main(int argc, char * argv[])
{
    if (argc != 2) { return EXIT_FAILURE; }
    handle_cases<int, Params<1>, Params<3>, Params<4>, Params<9>, Params<11>>(std::stoi(argv[1]));
}