Динамическая отправка в шаблонную функцию C ++ - PullRequest
0 голосов
/ 12 сентября 2018

У меня есть шаблонная функция (в моем случае ядро ​​cuda), где есть небольшое количество логических параметров шаблона, которые можно выбирать между во время выполнения.Я счастлив создавать все перестановки во время компиляции и динамически отправлять, например, так (для логических b0, b1, b2):

if (b0) {
    if (b1) {
        if (b2) {
            myFunc<true,true,true,otherArgs>(args);
        } else {
            myFunc<true,true,false,otherArgs>(args);
        }
    } else {
        if(b2) {
            myFunc<true,false,true,otherArgs>(args);
        } else {
            myFunc<true,false,false,otherArgs>(args);
        }
    }
} else {
    if(b1) {
        if(b2) {
            myFunc<false,true,true,otherArgs>(args);
        } else {
            myFunc<false,true,false,otherArgs>(args);
        }
    } else {
        if(b2) {
            myFunc<false,false,true,otherArgs>(args);
        } else {
            myFunc<false,false,false,otherArgs>(args);
        }
    }
}

Это раздражает писать и становится экспоненциально хуже, если я в конечном итоге сa b3 и b4.

Есть ли простой способ переписать это в более сжатой форме в C ++ 11/14 без использования больших внешних библиотек (например, boost)?Что-то вроде:

const auto dispatcher = construct_dispatcher<bool, 3>(myFunc);

...

dispatcher(b0,b1,b2,otherArgs,args);

Ответы [ 2 ]

0 голосов
/ 12 сентября 2018

Нет проблем.

template<bool b>
using kbool = std::integral_constant<bool, b>;

template<std::size_t max>
struct dispatch_bools {
  template<std::size_t N, class F, class...Bools>
  void operator()( std::array<bool, N> const& input, F&& continuation, Bools... )
  {
    if (input[max-1])
      dispatch_bools<max-1>{}( input, continuation, kbool<true>{}, Bools{}... );
    else
      dispatch_bools<max-1>{}( input, continuation, kbool<false>{}, Bools{}... );
  }
};
template<>
struct dispatch_bools<0> {
  template<std::size_t N, class F, class...Bools>
  void operator()( std::array<bool, N> const& input, F&& continuation, Bools... )
  {
     continuation( Bools{}... );
  }
};

Живой пример .

Итак, kbool - это переменная с булевой константой времени компиляции. dispatch_bools является вспомогательной структурой, которая имеет operator().

Этот operator() принимает массив времени выполнения bool с, и, начиная с max-1, переходит к появлению max, если ветвь / else, каждый повторяется в вызове dispatch_bools с вычислением еще одного bool во время компиляции.

Это генерирует 2 ^ максимальный код; именно тот код, который вы не хотите писать.

Продолжение передается до нижней рекурсии (где max=0). В этот момент все bool во время компиляции были собраны - мы вызываем continuation::operator(), передавая эти bool во время компиляции в качестве параметров функции.

Надеюсь, continuation::operator() - это шаблонная функция, которая может принимать bool во время компиляции. Если это так, существует 2 ^ max его экземпляров, каждое с каждой из 2 ^ max возможных комбинаций истина / ложь.


Чтобы использовать это для решения вашей проблемы в , вы просто делаете:

std::array<bool, 3> bargs={{b0, b1, b2}};
dispatch_bools<3>{}(bargs, [&](auto...Bargs){
  myFunc<decltype(Bargs)::value...,otherArgs>(args);
});

Это просто, потому что имеет auto lambdas; у него может быть шаблон operator() на лямбде. Превратить эти аргументы bool во время компиляции обратно в аргументы нетипичного шаблона легко.

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

template<class OtherArgs>
struct callMyFunc {
  Args args;
  template<class...Bools>
  void operator()(Bools...){
    myFunc<Bools::value...,otherArgs>(args);
  }
};

теперь используется:

std::array<bool, 3> bargs={{b0, b1, b2}};
dispatch_bools<3>{}(bargs, callMyFunc<otherArgs>{args});

Это в основном ручная запись того, что сделает лямбда.


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

Если вам нужна эта функция в , вы можете написать много кода decltype или использовать этот макрос:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

и напишите тело dispatch_bools как:

шаблон auto operator () (std :: array const & input, F && продолжение, Bools ...) ВОЗВРАТ ( (Вход [макс-1])? dispatch_bools {} (ввод, продолжение, kbool {}, Bools {} ...) : dispatch_bools {} (ввод, продолжение, kbool {}, Bools {} ...) ) и аналогичные для <0> специализации, и получить вычет возврата в стиле в .

RETURNS делает вывод типов возврата однострочными функциями тривиальными.

0 голосов
/ 12 сентября 2018

Есть ли простой способ? Нет. Можно ли это сделать, используя нечестивый беспорядок искаженных шаблонов? Конечно, почему бы и нет.

Осуществление

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

template <bool... Acc>
struct MyFuncWrapper {
  template <typename T>
  void operator()(T&& extra) const {
    return myFunc<Acc...>(std::forward<T&&>(extra));
  }
};

Это просто класс, для которого MyFuncWrapper<...>()(extra) эквивалентно myFunc<...>(extra).

Теперь давайте создадим нашего диспетчера.

template <template <bool...> class Func, typename Args, bool... Acc>
struct Dispatcher {

  auto dispatch(Args&& args) const {
    return Func<Acc...>()(std::forward<Args&&>(args));
  }

  template <typename... Bools>
  auto dispatch(Args&& args, bool head, Bools... tail) const {
    return head ?
      Dispatcher<Func, Args, Acc..., true >().dispatch(std::forward<Args&&>(args), tail...) :
      Dispatcher<Func, Args, Acc..., false>().dispatch(std::forward<Args&&>(args), tail...);
  }

};

Уфф, тут есть что объяснить. Класс Dispatcher имеет два аргумента шаблона, а затем список переменных. Первые два аргумента просты: функция, которую мы хотим вызвать (как класс), и «дополнительный» тип аргумента. Аргумент variadic будет начинаться с нуля, и мы будем использовать его в качестве аккумулятора во время рекурсии (аналогично аккумулятору, когда вы выполняете оптимизацию хвостового вызова ), чтобы накапливать логический список шаблона.

dispatch - это просто рекурсивная функция шаблона. Базовый случай - это когда у нас не осталось никаких аргументов, поэтому мы просто вызываем функцию с аргументами, которые мы накопили до сих пор. В рекурсивном случае используется условие, в котором мы накапливаем true, если логическое значение равно true, и false, если оно равно false.

.

Мы можем назвать это с

Dispatcher<MyFuncWrapper, TypeOfExtraArgument>()
    .dispatch(extraArgument, true, true, false);

Однако это немного многословно, поэтому мы можем написать макрос, чтобы сделать его более доступным. 1

#define DISPATCH(F, A, ...) Dispatcher<F, decltype(A)>().dispatch(A, __VA_ARGS__);

Теперь наш звонок

DISPATCH(MyFuncWrapper, extraArgument, true, true, false);

Полный исполняемый пример

Включает пример реализации myFunc.

#include <utility>
#include <iostream>

#define DISPATCH(F, A, ...) Dispatcher<F, decltype(A)>().dispatch(A, __VA_ARGS__);

template <bool a, bool b, bool c, typename T>
void myFunc(T&& extra) {
  std::cout << a << " " << b << " " << c << " " << extra << std::endl;
}

template <bool... Acc>
struct MyFuncWrapper {
  template <typename T>
  void operator()(T&& extra) const {
    return myFunc<Acc...>(std::forward<T&&>(extra));
  }
};

template <template <bool...> class Func, typename Args, bool... Acc>
struct Dispatcher {

  auto dispatch(Args&& args) const {
    return Func<Acc...>()(std::forward<Args&&>(args));
  }

  template <typename... Bools>
  auto dispatch(Args&& args, bool head, Bools... tail) const {
    return head ?
      Dispatcher<Func, Args, Acc..., true >().dispatch(std::forward<Args&&>(args), tail...) :
      Dispatcher<Func, Args, Acc..., false>().dispatch(std::forward<Args&&>(args), tail...);
  }

};

int main() {
  DISPATCH(MyFuncWrapper, 17, true, true, false);
  DISPATCH(MyFuncWrapper, 22, true, false, true);
  DISPATCH(MyFuncWrapper, -9, false, false, false);
}

Заключительные записки

Реализация, представленная выше, также позволит myFunc возвращать значения, хотя в вашем примере был только тип возврата void, поэтому я не уверен, что вам это понадобится. Как написано, реализация требует C ++ 14 для auto возвращаемых типов. Если вы хотите сделать это в C ++ 11, вы можете либо изменить все возвращаемые типы на void (больше не может ничего возвращать из myFunc), либо вы можете попытаться объединить возвращаемые типы с помощью decltype , Если вы хотите сделать это в C ++ 98, ... ... ... ... удачи


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

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