【问题标题】:Call sequence of template function for sequence of template parameters模板参数序列的模板函数调用序列
【发布时间】:2014-02-08 19:53:20
【问题描述】:

假设我有几个模板函数,例如:

template <int I> void f();
template <int I> void g();
template <int I> void h();

如何为模板参数序列调用这些函数的序列?

换句话说,我需要这样的行为:

{some template magic}<1, 5>(f); // This is pseudocode, I don't need exactly this format of calling.

展开到:

f<1>();
f<2>();
f<3>();
f<4>();
f<5>();

而且我需要相同的方法来处理我的每个函数(不仅是 f,还包括 g 和 h),而不需要为每个函数编写大而笨拙的结构。

我可以使用 C++11,甚至已经在最新开发的 gcc 版本 C++1y/C++14 功能(http://gcc.gnu.org/projects/cxx1y.html)中实现,例如多态 lambda。

【问题讨论】:

  • 如果您只将其用于整数,您是否还需要模板,或者您也希望能够使用其他类型?
  • 我需要使用我的枚举类型(它具有 size_t 基础类型)。所以,现在我只需要整数类型的解决方案。让我们假设它是通常的 int。

标签: c++ templates c++11 c++14


【解决方案1】:

具有 C++1y 特性。与其直接调用函数并将模板参数作为模板参数传递,不如创建一个 lambda,它接受一个 函数参数,其中包含模板参数作为其类型的一部分。即

f<42>();
[](std::integral_constant<int, 42> x) { f<x.value>(); }
[](auto x) { f<x.value>(); }

有了这个想法,我们可以传递函数模板f,当包装成这样一个多态的 lambda 时。这对于任何类型的重载集都是可能的,这是普通 lambda 无法做到的事情之一。

要使用一系列模板参数调用f,我们需要用于索引扩展技巧的公共索引类。这些将在 C++1y 标准库中。例如,Coliru 的 clang++ 编译器仍然使用没有 AFAIK 的旧 libstdc++。但是我们可以自己写:

#include <utility>

using std::integral_constant;
using std::integer_sequence;       // C++1y StdLib
using std::make_integer_sequence;  // C++1y StdLib
// C++11 implementation of those two C++1y StdLib classes:
/*
template<class T, int...> struct integer_sequence {};
template<class T, int N, int... Is>
struct make_integer_sequence : make_integer_sequence<T, N-1, N-1, Is...> {};
template<class T, int... Is>
struct make_integer_sequence<T, 0, Is...> : integer_sequence<T, Is...> {};
*/

当我们编写make_integer_sequence&lt;int, 5&gt; 时,我们会得到一个派生自integer_sequence&lt;int, 0, 1, 2, 3, 4&gt; 的类型。从后一种类型,我们可以推导出指数:

template<int... Indices> void example(integer_sequence<int, Indices...>);

在这个函数中,我们可以访问索引作为参数包。我们将使用索引来调用lamba /函数对象f,如下所示(不是问题中的函数模板f):

f( integral_constant<int, Indices>{} )...
// i.e.
f( integral_constant<int, 0>{} ),
f( integral_constant<int, 1>{} ),
f( integral_constant<int, 2>{} ),
// and so on

参数包只能在某些情况下进行扩展。通常,您会将包扩展为初始化程序(例如,虚拟数组),因为保证对它们的评估是有序的(感谢 Johannes Schaub)。可以使用类类型来代替数组,例如

struct expand { constexpr expand(...) {} };
// usage:
expand { pattern... };

一个虚拟数组如下所示:

int expand[] = { pattern... };
(void)expand; // silence compiler warning: `expand` not used

另一个棘手的部分是处理返回 void 作为模式的函数。如果我们将函数调用与逗号运算符结合起来,我们总是会得到一个结果

(f(argument), 0) // always has type int and value 0

要中断任何现有的重载逗号运算符,请添加 void()

(f(argument), void(), 0)

最后,结合以上所有来创造魔法:

template<int beg, class F, int... Is>
constexpr void magic(F f, integer_sequence<int, Is...>)
{
    int expand[] = { (f(integral_constant<int, beg+Is>{}), void(), 0)... };
    (void)expand;
}

template<int beg, int end, class F>
constexpr auto magic(F f)
{
    //                                              v~~~~~~~v see below (*)
    return magic<beg>(f, make_integer_sequence<int, end-beg+1>{});
}

使用示例:

#include <iostream>
template<int N> void f() { std::cout << N << "\n"; }

int main()
{
    //magic<1, 5>( [](auto x) { f<decltype(x)::value>(); } );
    magic<1, 5>( [](auto x) { f<x.value>(); } );
}

(*) 恕我直言end-beg+1 是不好的做法。 StdLib 使用[begin, end) 形式的半开范围是有原因的:空范围就是[begin, begin)。由于 StdLib 使用半开范围,因此在此处使用封闭范围可能会不一致。 (我知道的 StdLib 中有一个例外,它与 PRNG 和最大整数值有关。)

我建议您将magic 界面设计为采用半开范围,即

magic<1, 6>( [](auto x) { f<x.value>(); } ); // [1, 6) i.e. {1,2,3,4,5}

实现

template<int beg, int end, class F>
constexpr auto magic(F f)
{
    //                                              v~~~~~v
    return magic<beg>(f, make_integer_sequence<int, end-beg>{});
}

注意奇怪的+1 消失了。

【讨论】:

  • discard 需要可变参数,不是吗?
  • @jrok 确实,现在已修复,经过测试,有效,live example
  • template&lt;class T&gt; void discard(T&amp;&amp;...) {} 这是老派的省略号,而不是包扩展(省略逗号是合法的,请看!)。正确:template&lt;class ...T&gt; void discard(T&amp;&amp;...) {}
  • @jrok 哎呀,你是对的 m( 不过没关系,因为传递的参数始终是 int。也许使用省略号会更好,因为那样不会需要实例化一个模板。
  • 同意,如果我们确定它始终是标量类型。
【解决方案2】:

使用具体化函数和模板模板参数:

#include <iostream>

template<int I> class f {
public:
  static void call() {
    std::cout << I << '\n';
  }
};

template<template<int I> class X, int I, int J> class magic {
 public:
  static void call() {
    X<I>::call();
    magic::call();
  }
};

template<template<int I> class X, int I> class magic<X,I,I> {
public:
  static void call() {
    X<I>::call();
  }
};

int main(int argc, char** argv) {
  magic<f,2,6>::call();
  return 0;
}

【讨论】:

  • 这是一个有趣的解决方案。我说的是使用“template class X”模板参数。我什至不知道这是可能的。是否可以对功能使用相同的机制,而不是类?还是只能与类一起使用?
  • @Vasity,由于您不能将函数作为模板参数传递,因此您不能像这样传递函数。神奇的“函数”被实现为一个类,因此您可以使用模板专业化。
  • 我已经尝试过这个解决方案,但是我的程序打印了“2”数百次,然后它就崩溃了。您应该通过修改来编辑您的答案: magic::call();进入魔法::call();
猜你喜欢
  • 2011-08-08
  • 2021-10-01
  • 1970-01-01
  • 2013-08-02
  • 2011-05-27
  • 2017-06-26
  • 1970-01-01
  • 1970-01-01
  • 2016-01-30
相关资源
最近更新 更多