用于模拟运行时数字模板参数的成语?

时间:2016-08-12 09:34:06

标签: c++ templates reflection idioms

假设我们有

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;

3 个答案:

答案 0 :(得分:1)

为了简化这一过程,我将围绕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

答案 1 :(得分:1)

尽管其他两个答案相当通用,但是编译器很难优化它们。我目前在非常相似的情况下使用以下解决方案:

#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,出于说明性原因,该函数仅返回其模板参数,而函数caller_of_tf(y)则要给定运行时参数{{ 1}}。它本质上依赖于首先构造一个适当大小的参数包,然后使用短路tf<X>运算符扩展该参数包,该运算符严格地仅在第一个参数为true时才对其第二个参数求值。然后,我们只需将运行时参数与参数包中的每个元素进行比较。

此解决方案的优点是,优化很简单,例如Clang将上面的y转换为检查&&小于16并返回该值。 GCC的最佳化略差,但仍设法仅使用if-else链。对einpoklum发布的解决方案执行相同操作会导致生成更多的程序集(例如,使用GCC)。不利的一面当然是上面的解决方案更具体。

答案 2 :(得分:0)

这是@TartanLlama对无参数函数的解决方案的扩展,该函数具有任意数量的参数。它还具有避免在扩展为lambda时无法正确扩展可变参数模板参数包的GCC错误(版本8之前)的额外好处。

#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