Can you call a compile-time instantiated template function with a run-time specified variable?

Viewed 75

I have a function with an integer template parameter:

template<int N>
int func() {
    return N;
}

I explicitly instantiate the template for all template arguments I might provide:

template int func<1>();
template int func<2>();
template int func<3>();
//...

I then want to call func with a run-time specified integer n:

//This code does not compile    
int main() {
        int n;
        std::cin >> n;
        // check that n is one of the allowed values
        std::cout << func<n>(); // can fail if given a bad n
        return 0;
    }

Are there modifications I can make to this code to call func<n>() with n specified at run-time (say 0 < n < 20)?

4 Answers

Yes, it's possible, but not quite the way you're trying, because the template syntax like F<N> requires compile time values. However, once instantiated, they are regular functions and you can take function pointers to them, or put them in std::function, etc.

You can, for example, build an array for function pointers that allow access by a runtime index, something as follows:

template <int N>
int f() { return N; }  // your function(s)

int main()
{
    using Func = int(*)(); 
    Func funcs[]{f<0>, f<1>, f<2>, };  // array of instances of f

    // call a function via runtime value stored in n:
    int n = 2;
    funcs[n]();
}

Give the runtime program a list of functions to choose from and have it pick using an index (live example):

template<std::size_t... Indices>
int call_func_impl(std::index_sequence<Indices...>, int n) {
    using FuncType = int(*)();
    static const FuncType funcs[] = {func<Indices>...};
    return funcs[n]();
}

template<int MaxN>
int call_func(int n) {
    return call_func_impl(std::make_index_sequence<MaxN>{}, n);
}

...

call_func<20>(n);

Naturally, you could create the array manually (func<0>, func<1>, ...), but this wraps it up a bit more nicely. If you need a sequence other than 0 through N-1, use std::integer_sequence. If you need a non-sequence, there's no ready-made helper, though you can adapt one of the above if there's a pattern to arrive at the values you want (e.g., multiply each index_sequence element by 2 to get even numbers).


For completeness, C++20 enables a lovely (at least if you're used to worse) pattern for reducing copy-paste arguments:

template<int MaxN>
int call_func(int n) {
    const auto impl = [n]<std::size_t... Indices>(std::index_sequence<Indices...>) {
        using FuncType = int(*)();
        static const FuncType funcs[] = {func<Indices>...};
        return funcs[n]();
    };

    return impl(std::make_index_sequence<MaxN>{});
}

Here is a solution with simple caller function allowing to call your func when using integers from 1 to 20

#include <cassert>
#include <iostream>

template<int N>
int func() { return N; }

template <int N>
int callFunc(int n)
{
  if (n==N) return func<N>();
  else return callFunc<N-1>(n);
}

template <>
int callFunc<0>(int n)
{
  assert(false);
  return 0;
}

#define MAX_N 20
int main() {
  int n;
  std::cin >> n;
  std::cout << callFunc<MAX_N>(n);
  return 0;
}

If you can use C++17, another way using folding

#include <utility>
#include <iostream>

template <int N>
int func()
 { return N; }

template <int ... Is>
int func2 (int n, std::integer_sequence<int, Is...>)
 {
   int ret {};

   if ( false == ( ... || (Is == n ? (ret = func<Is>(), true) : false) ) )
      throw std::runtime_error{"no matching func()"};

   return ret;
 }

template <int I = 20>
int func3 (int n)
 { return func2(n, std::make_integer_sequence<int, I>{}); }

int main()
 {
   int n;

   std::cin >> n;

   std::cout << func3(n) << std::endl;
 }

Starting from C++20, you can also use template lambdas, so you can also write

#include <utility>
#include <iostream>

template <int N>
int func()
 { return N; }

int main()
 {
   int n;

   std::cin >> n;

   std::cout << []<int ... Is>(int n, std::integer_sequence<int, Is...>)
    {
      int ret {};

      if ( false == ( ... || (Is == n ? (ret = func<Is>(), true) : false) ) )
         throw std::runtime_error{"no matching func()"};

      return ret;
    }(n, std::make_integer_sequence<int, 20>{}) << std::endl;
 }
Related