2

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).

2
  • Well, for as simple function as hello, it might even be faster to pass the argument at runtime rather than use a lookup table. Commented Nov 7, 2019 at 0:24
  • @eerorika - definitely, if hello really just contained a printf, there would be no point. In reality, hello has an implementation which requires v to be a compile-time known value. Commented Nov 7, 2019 at 0:26

3 Answers 3

4

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

Sign up to request clarification or add additional context in comments.

Comments

2

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.

4 Comments

std::make_index_sequence might be slightly simpler option here.
@eerorika, sure, I used ints since that's what OP used in the question.
Did you test your solution? Because it doesn't compile
The addition in the fold expression is unnecessary. Just use the comma operator as the binary operator: ((res[Is] = hello<Is>), ...)
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.