调用模板函数序列的模板函数序列

时间:2014-02-08 19:53:21

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

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

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。

2 个答案:

答案 0 :(得分:7)

使用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 ++编译器仍然使用较旧的libstdc ++,它没有AFAIK。但我们可以写自己的:

#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<int, 5>时,我们会得到一个源自integer_sequence<int, 0, 1, 2, 3, 4>的类型。从后一种类型,我们可以推断出指数:

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

在此函数中,我们可以将索引作为参数包进行访问。我们将使用索引来调用lamba / function对象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消失了。

答案 1 :(得分:5)

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

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