具有 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<int, 5> 时,我们会得到一个派生自integer_sequence<int, 0, 1, 2, 3, 4> 的类型。从后一种类型,我们可以推导出指数:
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 消失了。