如何在C++11中编写一个带有数量可变的同构非POD函数参数的模板函数?
例如,假设我们想要为任何定义小于"operator<“的类型编写一个min函数,如下所示:
// pseduo-code...
template<class T...>
T min(T x1, T x2, ..., T xn)
{
T lowest = x1;
for (T x : {x2,...,xn})
if (x < lowest)
lowest = x;
return lowest;
}上面的是非法的C++11,你怎么合法地写它?
发布于 2012-03-19 06:22:58
同构?只需使用 std::initializer_list.
template <typename T>
T min_impl(std::initializer_list<T> values)
{
return *std::min_element(values.begin(), values.end());
}
...
return min_impl({8, 5, 4, 1, 6});(正如@Jesse所指出的,这等同于标准库中的std::min。)
如果您不喜欢额外的大括号,可以创建一个可变模板,该模板转发给初始化器列表实现:
template <typename... T>
auto min(T&&... args) -> decltype(min_impl({std::forward<T>(args)...}))
{
return min_impl({std::forward<T>(args)...});
}
...
return min(8, 5, 1, 4, 6);发布于 2012-03-19 07:28:34
首先,可变模板不包含“单个类型的可变数量的参数”的方式。当您使用可变模板时,您会得到一个参数包,它是一组零个或多个参数,每个参数都可能具有唯一的类型:
template<typename... Ts> void foo(Ts... ts);...标记只定义了这些参数包(和vararg函数,但这不是重点)的含义。所以你不能把它和非参数包一起使用:
template<typename T> void foo(T... t); // error
template<typename T> void foo(T t...); // error其次,一旦有了参数包,就不能像使用基于范围的for循环那样遍历参数。相反,您必须以函数式风格编写算法,使用参数包扩展来“剥离”参数包中的参数。
// single argument base case
template<typename T>
void foo(T t) {
std::cout << t;
}
template<typename T,typename... Us>
void foo(T t,Us... us) {
foo(t) // handle first argument using single argument base case, foo(T t)
foo(us...); // 'recurse' with one less argument, until the parameter pack
// only has one argument, then overload resolution will select foo(T t)
}尽管可变模板不能直接支持您想要的内容,但您可以使用enable_if并使用'SFINAE‘规则来施加此约束。首先,这里是一个没有约束的版本:
#include <type_traits>
#include <utility>
template<class T>
T min(T t) {
return t;
}
template<class T,class... Us>
typename std::common_type<T,Us...>::type
min(T t,Us... us)
{
auto lowest = min(us...);
return t<lowest ? t : lowest;
}
int main() {
min(1,2,3);
}然后应用enable_if以确保所有类型都是相同的。
template<class T,class... Us>
typename std::enable_if<
std::is_same<T,typename std::common_type<Us...>::type>::value,
T>::type
min(T t,Us... us)
{
auto lowest = min(us...);
return t<lowest ? t : lowest;
}根据is_same的说法,上述修改后的实现将防止在参数不完全相同的任何时候使用该函数。
如果没有必要,你最好不要使用这些技巧。像KennyTM建议的那样使用initializer_list可能是一个更好的想法。事实上,如果你真的实现了min和max,那么你可以省去你自己的麻烦,因为标准库已经包含了接受initializer_list的重载。
is_same<T,typename common_type<Us...>::type>是如何工作的?
因为min()只有一个参数版本,所以只有当有两个或更多参数时,才会选择可变版本。这意味着sizeof...(Us)至少是一个。在它恰好是1的情况下,common_type<Us...>返回该类型单个类型,并且is_same<T,common_type<Us...>>确保这两个类型是相同的。
min()的各种实现调用min(us...)。只要这个调用只在Us...中的所有类型都相同时才起作用,我们知道commont_type<Us...>告诉我们那个类型是什么,并且is_same<T,common_type<Us...>>确保T也是那个类型。
所以我们知道,只有当a和b是同一类型时,min(a,b)才能工作。我们知道min(c,a,b)调用min(a,b),所以只有当a和b是同一类型,并且c也是同一类型时,才能调用min(c,a,b)。min(d,c,a,b)调用min(c,a,b),因此我们知道只有当c、a和b都是相同类型,并且d也是相同类型时,才能调用b。等。
发布于 2012-03-19 06:04:00
这有点令人费解,但如果你想要异类参数,这就是你要得到的:
#include <iostream>
#include <type_traits>
template<typename F, typename T, typename Arg>
auto fold(F f, T&& t, Arg&& a)
-> decltype(f(std::forward<T>(t), std::forward<Arg>(a)))
{ return f(std::forward<T>(t), std::forward<Arg>(a)); }
template<typename F, typename T, typename Head, typename... Args>
auto fold(F f, T&& init, Head&& h, Args&&... args)
-> decltype(f(std::forward<T>(init), std::forward<Head>(h)))
{
return fold(f, f(std::forward<T>(init), std::forward<Head>(h)),
std::forward<Args>(args)...);
}
// polymorphic less
struct p_less {
template<typename T, typename U>
typename std::common_type<T, U>::type
operator()(T&& t, U&& u) const {
return t < u ? t : u;
}
};
// heterogeneous arguments possible
template<typename Head, typename... Args>
auto min(Head&& h, Args&&... args) -> typename std::common_type<Head, Args...>::type
{
return fold(p_less(), std::forward<Head>(h),
std::forward<Args>(args)...);
}
// only considers homogeneous arguments
template<typename Head, typename... Args>
auto hmin(Head&& h, Args&&... args) -> Head
{
return fold([](Head x, Head y) -> Head { return x < y ? x : y; },
std::forward<Head>(h), std::forward<Args>(args)...);
}
int main()
{
double x = 2.0, x2 = 3.0;
int y = 2;
auto d1 = min(3, 4.0, 2.f, 6UL);
auto d2 = min(x, y);
auto d3 = hmin(x, x2);
auto b = hmin(3, 2, 7, 10);
std::cout << d1 << std::endl;
std::cout << d2 << std::endl;
std::cout << d3 << std::endl;
std::cout << b << std::endl;
return 0;
}然而,第一个版本要有趣得多。common_type查找所有参数都可以强制转换的类型。这是必要的,因为具有返回其中任何一个的异类参数的函数需要找到该类型。然而,这可以使用boost::variant来规避,但对于内置类型来说,这是没有意义的,因为无论如何都必须对它们进行强制比较。
https://stackoverflow.com/questions/9762531
复制相似问题