我们假设我有几个模板函数,例如:
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。
答案 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;
}