Инициализация массива указателей на функцию constexpr - PullRequest
1 голос
/ 07 ноября 2019

Я хотел бы преобразовать значение времени выполнения int v в вызов соответствующей функции с нетипичным параметром шаблона v, например, template <int v> void hello().

Вот способ грубой силызаписывая это:

using fn_type = void();

template <int v>
void hello() {
    // of course ITRW this function requires v to be a
    // constexpr value
    printf("hello: %d\n", v);
}

static std::array<fn_type *, 3> lut = {
    hello<0>,
    hello<1>,
    hello<2>
};

void hello_dispatch(int v) {
    lut[v](); // we don't handle OOB values b/c we are naughty like that
}

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

Как можно инициализировать LUT 2 во время компиляции без явного перечисления различных экземпляров hello<0>, hello<1>, ... в инициализаторе?

Вот что я придумал:

template <size_t I, size_t MAX>
constexpr void make_helper(std::array<fn_type *, MAX>& a) {
    if constexpr (I < MAX) {
        a[I] = hello<I>;
        make_helper<I + 1, MAX>(a);
    }
}

template <size_t MAX>
constexpr std::array<fn_type *, MAX> make_lut() {
    std::array<fn_type *, MAX> ret{};
    make_helper<0, MAX>(ret);
    return ret;
}


static constexpr std::array<fn_type *, 3> lut2 = make_lut<3>();

Должно быть что-то более простое, лучшее и более идиоматическое в C ++ 17 - в частности, без необходимости рекурсии.


2 Или, если этопроблема XY, как я могу реализовать hello_dispatch без LUT (но, по крайней мере, с эффективностью LUT).

Ответы [ 3 ]

3 голосов
/ 07 ноября 2019

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

template<std::size_t... I>
constexpr auto make_helper(std::index_sequence<I...>) {
    return std::array<fn_type *, sizeof...(I)> { hello<I>... };
}
template <std::size_t MAX>
constexpr auto make_lut() {
    return make_helper(std::make_index_sequence<MAX>{});
}

LIVE

1 голос
/ 07 ноября 2019

Использование std::integer_sequence и выражений сгиба :

template <int... Is>
constexpr std::array<fn_type*, sizeof...(Is)> make_lut(std::integer_sequence<int, Is...>) {
    std::array<fn_type*, sizeof...(Is)> res{};
    ((res[Is] = &hello<Is>), ...);
    return res;
}

template <int Max>
constexpr auto make_lut() {
    return make_lut(std::make_integer_sequence<int, Max>{});
}

static constexpr auto lut2 = make_lut<3>();
  • std::make_integer_sequence<int, N>{} создает объект типа std::integer_sequence<int, 0, 1, 2, ..., N - 1>.
  • Мы фиксируем эти значения с помощью первого шаблона функции и присваиваем каждому hello<N> значение res[N] с выражением сгиба res[Is] = &hello<Is>.
  • Мы можем применить оператор запятой к значению в левой части сгиба ....
0 голосов
/ 07 ноября 2019

А теперь, для чего-то совершенно другого ...

Если вы можете использовать C ++ 20, вы можете использовать template-lambdas, и вы можете полностью избежать массива lut

#include <iostream>
#include <utility>

template <int v>
void hello()
 { std::cout << "hello: " << v << std::endl; }

void hello_dispatch (int v)
 {
   [&]<int ... Is>(std::integer_sequence<int, Is...> const &)
      { ((v == Is ? (hello<Is>(), 0) : 0), ...); }
         (std::make_integer_sequence<int, 100u>{}); // 100 is top limit (former lut size)
 }

int main ()
 {
   hello_dispatch(42);
 }

Если вы можете использовать только C ++ 17 ... не так элегантно, но вы можете использовать рекурсивную универсальную лямбду

#include <iostream>
#include <utility>

template <int v>
void hello()
 { std::cout << "hello: " << v << std::endl; }

template <int I>
using IC = std::integral_constant<int, I>;

void hello_dispatch (int v)
 {
   auto lf = [&](auto self, auto ic)
    { if constexpr ( ic < 100 )
         v == ic ? (hello<ic>(), 0)
                 : (self(self, IC<ic+1>{}), 0); };

   lf(lf, IC<0>{});
 }

int main ()
 {
   hello_dispatch(42);
 }
...