C++11:变分齐次非POD模板函数参数
C++11: Variadic Homogeneous Non-POD Template Function Arguments?
如何在C++11中编写一个采用可变数量同质非POD函数参数的模板函数?
例如,假设我们想为定义小于"运算符<"的任何类型编写一个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,你会如何合法地写它?
同质只需使用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);
首先,可变模板不包含"单个类型的可变数量的参数"。当你使用可变模板时,你会得到一个参数包,它是一组零个或多个参数,每个参数都有一个可能唯一的类型:
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)
至少是一个。在它恰好是一个的情况下,common_type<Us...>
返回类型single类型,而is_same<T,common_type<Us...>>
确保这两个类型相同。
min()
的变址实现调用min(us...)
。只要这个调用只有在Us...
中的所有类型都相同时才有效,我们就知道commont_type<Us...>
告诉什么是该类型,而is_same<T,common_type<Us...>>
确保T也是同一类型。
因此,我们知道min(a,b)
只有当a
和b
是同一类型时才有效。我们知道min(c,a,b)
调用min(a,b)
,所以只有当a
和b
是同一类型时,才能调用min(c,a,b)
,此外,如果c
也是同一类型。min(d,c,a,b)
调用min(c,a,b)
,因此我们知道只有当c
、a
和b
都是同一类型时,才能调用min(d,c,a,b)
,此外,如果d
也是同一类型。等等
这有点复杂,但如果你想的话,这就是你想要的异构自变量:
#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
规避,但对于内置类型,这是这是毫无意义的,因为无论如何都要强迫他们进行比较。
另一种可能性:
template <typename T, typename... T2>
T min(T x1, T2... rest);
template <typename T>
T min(T x)
{
return x;
}
template <typename T, typename... T2>
T min(T x1, T2... rest)
{
return std::min (x1, min (rest...));
}
虽然这似乎并不能强制实现一致性,但使用例如int
和long
进行调用会产生编译错误。不幸的是,我不太擅长用标准语来判断它是否应该是这样。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- C++定义构造函数使对象成为非 POD
- 约束类模板函数以接受特定的 POD 类型
- 我可以说服自动生成的构造函数将我的 POD 类成员归零吗?
- 在不使用默认构造函数的情况下声明 POD 结构时,会实例化什么?
- C++函数中的多线程静态 POD 初始化
- 初始化新对象时C++默认构造函数和 POD 问题
- 为什么具有已删除复制构造函数的结构不是 POD 类型
- 构造函数初始化列表中POD类型的初始化
- 非 pod 类型的布局(因为具有默认构造函数)
- 默认的构造函数和POD
- C++11中POD的构造函数要求
- 具有用户定义构造函数的普通和 POD 类型
- POD 类对象初始化是否需要构造函数
- 我们是否需要为分配"placement new" "simple POD classes"显式调用析构函数?
- 函数作用域的静态非 Pod 对象初始化
- C++11:变分齐次非POD模板函数参数
- (POD)结构上是否需要移动构造函数
- 默认构造函数、POD初始化和C++11中的隐式类型转换
- 对空的用户定义构造函数将如何初始化非静态非 POD 成员变量感到困惑