Idiom for simulating run-time numeric template parameters?

元气小坏坏 提交于 2019-11-29 16:46:43
TartanLlama

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

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!