模拟运行次数字模板参数的成语?

Idiom for simulating run-time numeric template parameters?

假设我们有

template <unsigned N> foo() { /* ... */ }

定义。现在,我想实施

do_foo(unsigned n);

调用 foo() 的相应变体。这不仅仅是一个合成的例子——这在现实生活中确实发生过(当然,不一定是 void-to-void 函数和一个模板参数,但我正在简化。当然,在 C++ 中,我们不能有以下内容:

do_foo(unsigned n) { foo<n>(); }

而我现在所做的是

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

当我知道 n 的有效范围限制在 n_1,...,n_k 时。但这是不合适的,当调用时间更长并且我需要多次复制一长串模板和常规参数时更是如此。

我正要开始研究一个宏来生成这些 switch 语句,当我开始思考也许有人已经在某个库中研究过这个并且可以分享他们所做的事情。如果没有,也许仍然可以使用某种 C++ 构造,它采用任意函数,具有任意序列的模板和非模板参数,包括一些数字模板参数,以及某种形式的一系列值,以生成一个包装器,它可以将该模板参数作为附加的 运行 时间参数,例如

auto& transformed_foo = magic<decltype(foo)>(foo)::transformed;

为了使这更容易,我将围绕 foo:

制作一个仿函数包装器
struct Foo {
    template <unsigned N>
    void operator()(std::integral_constant<unsigned,N>)
    { foo<N>(); }
};

现在我们可以画出访客的草图了:

template <std::size_t Start, std::size_t End, typename F>
void visit(F f, std::size_t n) {
    //magic
};

完成后,会这样调用:

visit<0, 10>(Foo{}, i);
// min^  ^max       

魔术将涉及使用索引技巧。我们将生成一个覆盖所需范围的索引序列,并将标签分派给助手:

visit<Start>(f, n, std::make_index_sequence<End-Start>{});

现在是实施的真正内容。我们将构建一个 std::functions 数组,然后使用运行时提供的值对其进行索引:

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

这当然可以变得更通用,但这应该为您提供一个很好的起点来应用于您的问题领域。

Live Demo

这是@TartanLlama 的无参数函数解决方案到具有任意数量参数的函数的扩展。它还具有规避 GCC 错误(版本 8 之前)的额外好处,即当扩展是 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

虽然其他两个答案相当通用,但编译器很难对其进行优化。我目前处于非常相似的情况,使用以下解决方案:

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

我们有一些模板函数 tf 出于说明原因,它只是 returns 它的模板参数和一个函数 caller_of_tf(y) 想要调用适当的 tf<X> 给定run-time 参数 y。它基本上依赖于首先构建一个 appropriately-sized 参数包,然后使用 short-circuiting && 运算符扩展这个参数包,如果第一个参数为真,它严格只评估它的第二个参数。然后我们简单地将 run-time 参数与参数包的每个元素进行比较。

这个解决方案的好处是它很容易优化,例如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)。当然,缺点是上面的解决方案更具体。