【问题标题】:Idiom for simulating run-time numeric template parameters?模拟运行时数字模板参数的成语?
【发布时间】:2016-12-19 06:32:28
【问题描述】:

假设我们有

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;

【问题讨论】:

  • 有什么阻止您将其设为constexpr 函数吗?喜欢this
  • @TartanLlama:您为我的问题考虑了错误的方向。我不想在 compile-time 使用额外的参数,我希望在运行时使用一个根据 N 的值编译非常不同的函数来获得更大的灵活性。
  • 酷,所以我猜你有一堆专业化,阻止你简单地做到constexpr。您可能可以做一些类似于在运行时索引std::tuples 的技巧。例如,请参阅this。如果我有时间,我会尝试写一个解决方案。
  • 类似this?
  • @TartanLlama:现在你说的是……索引序列和省略号扩展。好的。但也许你应该以this 为例来强调如何在编译时无法确定参数。

标签: c++ templates reflection idioms


【解决方案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. 我不能成为第一个想要这样做的人。这样的成语不是某个图书馆的一部分吗?一些 TS 或 Boost 中的某个地方? 2. 为什么需要对象包装器?不能使用函数指针吗?
  • @einpoklum 1. 也许,但我不知道有一个。 2.使用函数指针的问题是你需要在visit的调用点解析你指向的函数,所以它并没有真正起作用。您可能会想出一种在 C++14 中使用通用 lambda 的方法。
  • 我只是想用这个 - 并注意到它不能用 GCC 6.x 和 7.x 编译(!!)它用 clang 6.0(可能更早)编译,并且GCC 8.x。此外,此代码是 C++11(除了缺少的 std::index_sequence&lt;&gt;,您需要用自己的代码替换)。
  • 这里也没有真正的理由使用std::function
【解决方案2】:

这是@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

【讨论】:

    【解决方案3】:

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

    #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),它希望在给定运行时参数y的情况下调用适当的tf&lt;X&gt;。它本质上依赖于首先构造一个适当大小的参数包,然后使用短路&amp;&amp; 运算符扩展此参数包,该运算符严格仅在第一个参数为真时才评估其第二个参数。然后,我们只需将运行时参数与参数包的每个元素进行比较。

    这个解决方案的好处是它很容易优化,例如Clang 将上面的 a() 变成检查 x 是否小于 16 并返回它。 GCC 的优化程度稍差,但仍设法仅使用 if-else 链。对 einpoklum 发布的解决方案执行相同操作会导致生成更多程序集(例如使用 GCC)。当然,缺点是上面的解决方案更具体。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-23
      • 1970-01-01
      • 2016-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多