问题
Suppose we have
template <unsigned N> foo() { /* ... */ }
defined. Now, I want to implement
do_foo(unsigned n);
which calls the corresponding variant of foo()
. This is not merely a synthetic example - this does actually happen in real life (of course, not necessarily with void-to-void functions and just one template parameter, but I'm simplfying. Of course, in C++, we can't have the following:
do_foo(unsigned n) { foo<n>(); }
and what I do right now is
do_foo(unsigned n) {
switch(n) {
case n_1: foo<n_1>(); break;
case n_2: foo<n_2>(); break;
/* ... */
case n_k: foo<n_k>(); break;
}
}
when I know n is effectively limited in range to n_1,...,n_k. But this is unseemly, and much more so when the call is longer and I need to duplicate a long sequence of template and regular parameters many times.
I was about to start working on a macro to produce these switch statements, when I got to thinking maybe someone has already worked on this in some library and could share what they did. If not, perhaps it's still feasible to have some kind of C++ construct which takes an arbitrary function, with any sequence of template and non-template parameters including some numeric template parameter, and a sequence of values in some form, to produce a wrapper which can take that template parameter as an additional run-time parameter instead, e.g.
auto& transformed_foo = magic<decltype(foo)>(foo)::transformed;
回答1:
To make this easier, I'll make a functor wrapper around foo
:
struct Foo {
template <unsigned N>
void operator()(std::integral_constant<unsigned,N>)
{ foo<N>(); }
};
Now we can sketch out our visitor:
template <std::size_t Start, std::size_t End, typename F>
void visit(F f, std::size_t n) {
//magic
};
When it's finished, it'll get called like this:
visit<0, 10>(Foo{}, i);
// min^ ^max
The magic is going to involve using the indices trick. We'll generate an index sequence covering the range desired and tag-dispatch to a helper:
visit<Start>(f, n, std::make_index_sequence<End-Start>{});
Now the real meat of the implementation. We'll build up an array of std::functions
, then index it with the runtime-supplied value:
template <std::size_t Offset, std::size_t... Idx, typename F>
void visit(F f, std::size_t n, std::index_sequence<Idx...>) {
std::array<std::function<void()>, sizeof...(Idx)> funcs {{
[&f](){f(std::integral_constant<unsigned,Idx+Offset>{});}...
}};
funcs[n - Offset]();
};
This could certainly be made more generic, but this should give you a good starting point to apply to your problem domain.
Live Demo
回答2:
While the other two answers are fairly generic, they are a bit hard for the compiler to optimise. I currently in a very similar situation use the following solution:
#include <utility>
template<std::size_t x>
int tf() { return x; }
template<std::size_t... choices>
std::size_t caller_of_tf_impl(std::size_t y, std::index_sequence<choices...>) {
std::size_t z = 42;
( void( choices == y && (z = tf<choices>(), true) ), ...);
return z;
}
template<std::size_t max_x, typename Choices = std::make_index_sequence<max_x> >
std::size_t caller_of_tf(std::size_t y) {
return caller_of_tf_impl(y, Choices{});
}
int a(int x) {
constexpr std::size_t max_value = 15;
return caller_of_tf<max_value+1>(x);
}
where we have some templated function tf
which for illustrative reasons simply returns its template argument and a function caller_of_tf(y)
which wants to call the appropriate tf<X>
given a run-time argument y
. It essentially relies on first constructing an appropriately-sized argument pack and then expanding this argument pack using a short-circuiting &&
operator which strictly only evaluates its second argument if the first argument is true. We then simply compare the run-time parameter to each element of the parameter pack.
The nice thing about this solution is that it is straightforward to optimise, e.g. Clang turns a()
above into a check that x
is smaller than 16 and returns that. GCC is slightly less optimal but still manages to only use an if-else chain. Doing the same with the solution posted by einpoklum results in a lot more assembly being generated (e.g. with GCC). The downside, of course, is the solution above is more specific.
回答3:
This is an extension of @TartanLlama's solution for a no-argument function to a function with an arbitrary number of arguments. It also has the added benefit of circumventing a GCC bug (before version 8) of failing to properly expand variadic template parameter packs when the expansion is of a lambda.
#include <iostream>
#include <utility>
#include <array>
#include <functional>
struct Foo {
template <std::size_t N, typename... Ts> void operator()(std::integral_constant<std::size_t,N>, Ts... args)
{ foo<N>(std::forward<Ts>(args)...); }
};
template <std::size_t N, typename F, typename... Ts>
std::function<void(Ts...)> make_visitor(F f) {
return
[&f](Ts... args) {
f(std::integral_constant<std::size_t,N>{}, std::forward<Ts>(args)...);
};
}
template <std::size_t Offset, std::size_t... Idx, typename F, typename... Ts>
void visit(F f, std::index_sequence<Idx...>, std::size_t n, Ts... args) {
static std::array<std::function<void(Ts...)>, sizeof...(Idx)> funcs {{
make_visitor<Idx+Offset, F, Ts...>(f)...
}};
funcs[n-Offset](std::forward<Ts>(args)...);
};
template <std::size_t Start, std::size_t End, typename F, typename... Ts>
void visit(F f, std::size_t n, Ts... args) {
visit<Start>(f, std::make_index_sequence<End-Start>{}, n, std::forward<Ts>(args)...);
};
Live demo
来源:https://stackoverflow.com/questions/38914655/idiom-for-simulating-run-time-numeric-template-parameters