BeeOnRope
BeeOnRope

Reputation: 65046

Initializing a constexpr array of function pointers

I'd like to convert a runtime value int v into a call to a corresponding function with non-type template parameter v, e.g., template <int v> void hello().

Here's the brute force way of writing it:

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
}

I can live with that for 3 values, but this gets impractical with more values, or when the the limit is itself calculated from some other compile-time value.

How can initialize the LUT2 at compile-time without explicitly listing the various instantiations hello<0>, hello<1>, ... in the initializer?

This is what I came up with:

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>();

There has got to be something simpler, better and more idiomatic in C++17 - in particular, without needing recursion.


2 Or, in case this an an XY problem, how can I implement hello_dispatch without a LUT (but with at least the efficiency of the LUT).

Upvotes: 2

Views: 1199

Answers (3)

max66
max66

Reputation: 66230

And now, for something completely different...

If you can use C++20, you can use template-lambdas and you can completely avoid the lut array

#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);
 }

If you can use only C++17... not so elegant but you can use a recursive generic lambda

#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);
 }

Upvotes: 0

songyuanyao
songyuanyao

Reputation: 173004

You can initialize the std::array directly, i.e. don't need to assign the elements one by one with recursion.

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

Upvotes: 4

Fatih BAKIR
Fatih BAKIR

Reputation: 4735

Use std::integer_sequence and fold expressions:

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>{} creates an object of type std::integer_sequence<int, 0, 1, 2, ..., N - 1>.
  • We capture these values with the first function template and assign each hello<N> to res[N] with the fold expression res[Is] = &hello<Is>.
  • We can apply a comma operator to the value on the left hand side of the ... fold.

Upvotes: 2

Related Questions