C ++: Как программно определить типы шаблонов во время выполнения? - PullRequest
0 голосов
/ 02 ноября 2018

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

Скажите, что перечисление

Enum typenum {A, B, C}

И все эти функции шаблона определены и доступны во время выполнения (т. Е. Компилятор создает эти функции во время компиляции)

Alpha<A>::f()
Alpha<B>::f()
Alpha<C>::f()
Alpha<A,A>::f()
Alpha<A,B>::f()
Alpha<A,C>::f()
Alpha<B,A>::f()
Alpha<B,B>::f()
Alpha<B,C>::f()
Alpha<C,A>::f()
Alpha<C,B>::f()
Alpha<C,C>::f()
and combination of 3 enums, 4 enums...

Теперь я должен выбрать правильную функцию согласно входному вектору

void f(vector<enum> eVec){
    Alpha::f<eVec[0], eVec[1],... eVec[eVec.size() - 1]>() // <-------

Как мне это сделать? Один из способов сделать это - определить для каждого размера. Например:

if(eVec.size() == 1)
   Alpha<eVec[0]>::f()
else if(eVec.size() == 2)
   Alpha<eVec[0], eVec[1]>::f()

Это не будет масштабироваться, хотя. Есть ли какой-нибудь элегантный, масштабируемый способ сделать это.

Ответы [ 3 ]

0 голосов
/ 02 ноября 2018

И все эти функции шаблона определены и доступны во время выполнения (т. Е. Компилятор создает эти функции во время компиляции)

Вы уверены, что это хорошая идея? Потому что, если вы хотите выбрать значения шаблона времени выполнения, вы должны реализовать, время компиляции, все возможные комбинации Alpha<typeNumsValues...>::f(). Это невозможно, если вы не налагаете ограничение на длину для списка с вариациями, но это очень дорого с точки зрения вычислений, даже если существует относительно низкий предел.

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

enum typeEnum { A, B, C };

и шаблон класса variadic Alpha, со значениями шаблона typeEnum и методом static f() следующим образом

template <typeEnum ...>
struct Alpha
 { static void f () { /* do something */ } };

ваш f() может вызвать вариад f_helper()

void f (std::vector<typeEnum> const & eVec)
 { f_helper<>(eVec, 0u); }

реализовано следующим образом

template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
 { }

template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
    f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
 {
   if ( index < eVec.size() )
      switch ( eVec[index++] )
       {
         case A: f_helper<Tes..., A>(eVec, index); break;
         case B: f_helper<Tes..., B>(eVec, index); break;
         case C: f_helper<Tes..., C>(eVec, index); break;
       }
   else
      Alpha<Tes...>::f();
 }

Заметьте, что я установил очень низкий предел (5, sizeof...(Tes) < 6u) для длины вариационного списка, потому что число развитых Alpha растет в геометрической прогрессии.

Также обратите внимание, что я добавил бесполезную версию f_helper(); это необходимо, потому что рекурсивный вызов длиной менее 6 * может вызывать его с помощью переменного списка из 6 перечислений, которым нужно каким-то образом управлять.

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

#include <vector>    

enum typeEnum { A, B, C };

template <typeEnum ...>
struct Alpha
 { static void f () { } };

template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
 { }

template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
    f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
 {
   if ( index < eVec.size() )
      switch ( eVec[index++] )
       {
         case A: f_helper<Tes..., A>(eVec, index); break;
         case B: f_helper<Tes..., B>(eVec, index); break;
         case C: f_helper<Tes..., C>(eVec, index); break;
       }
   else
      Alpha<Tes...>::f();
 }

void f (std::vector<typeEnum> const & eVec)
 { f_helper<>(eVec, 0u); }   

int main ()
 {
   f({A, B, C, A});
 }
0 голосов
/ 02 ноября 2018

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

Предполагая, что у вас enum есть значение по умолчанию None и что у вас есть до 5 аргументов, вы можете определить карту следующим образом:

enum MyEnum { None = 0, A, B, C, D... };
using MyKey = std::tuple<MyEnum, MyEnum, MyEnum, MyEnum, MyEnum>;
using MyFunction = std::function<void()>;

Тогда у вас есть где-то карта функции (синглтон)

std::map<MyKey, MyFunction> myMap;

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

MyKey MakeKey(MyEnum e1, MyEnum e2 = None, MyEnum e3 = None, MyEnum e4 = None, MyEnum e5 = None)
{
    return std::make_tuple(e1, e2, e3, e4, e5);
}

myMap.emplace(MakeKey(A, B), [](){ /* some code */ });

MyEnum AtOrDefault(const vector<enum> &eVec, int index)
{
    return index < eVec.size() ? eVec[index] : None;
}

Тогда, если вы хотите вызвать соответствующую функцию из вектора, вы можете сделать:

void f(const vector<enum> &eVec)
{
    if (eVec.size() > 5) throw some_exception;

    MyKey key = std::make_typle(
        AtOrDefault(eVec, 0), 
        AtOrDefault(eVec, 1), 
        AtOrDefault(eVec, 2), 
        AtOrDefault(eVec, 3), 
        AtOrDefault(eVec, 4));

    auto &fn = myMap[key];
    fn();
}

Вы также можете использовать идею вычисления значения, предполагая, что вы знаете максимальное количество элементов в перечислении. Затем вы можете создать CombinedEnumType:

enum CombinedEnumType : uint32_t { };

и определил функцию

CombinedEnumType MakeCombinedEnumType(MyEnum e1, … MyEnum e5 = None) 
{
    const int MyEnumEcount = 5;
    MyEnum a[] = { e1, e2, e3, e4, e5 };

    uint32_t result = 0;
    for (auto & item : a) { result *= MyEnumEcount; result += item; }
    return static_cast<CombinedEnumType>(result);
}

Этот код только для идей. В реальном производственном коде вы должны использовать константы, собственные имена переменных, проверять, существует ли функция для данной комбинации…

0 голосов
/ 02 ноября 2018

Отвечая на мой собственный вопрос. Я понял, что этот подход не будет работать. Потому что когда мы пишем что-то вроде

Alpha<eVec[0]>::f()

Компилятор выдает ошибку - «ошибка: выражение должно иметь постоянное значение»

Таким образом, единственная альтернатива осталась

if(eVec.size() == 1){
    switch(eVec[0]){
        case A:
            Alpha<A>::f()
.....

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

...