我看过一些代码(https://stackoverflow.com/a/19209751/877329)使用递归"继承"修改模板参数包。这个建筑背后的想法是什么?它是如何工作的?
答案 0 :(得分:1)
<强>前言强>
虽然模板递归通常是必需的,但没有必要使用继承。也就是说,使用这种模式已经变得相当标准:
template <typename T, typename = void>
struct has_trait : std::false_type {};
template <typename T>
struct has_trait<T, std::enable_if_t<some_predicate>> : std::true_type {};
也就是说,标准提供了类型std::{true,false}_type
以方便使用,因为它们具有静态constexpr
value
成员。这种模式强烈表明递归继承是常见的并且受到鼓励。
递归继承
假设我想确定参数包是否包含T
类型的参数。为此,我将创建一个类型为
struct
template <bool B, typename T, typename ... ARGS>
struct pack_with_type_impl;
非类型参数B
是“匹配”参数; T
是我们有兴趣匹配的参数;并且ARGS...
是参数包。
我们将使用此模板的特化来处理各种情况。
清空参数包
我们来看参数包是空的情况。在这种情况下,专业化看起来像:
template <bool B, typename T>
struct pack_with_type_impl<B,T> : std::false_type {};
空参数包中显然没有类型T
,因此专门化继承自std::false_type
。
匹配参数包
现在让我们说已经确定参数包中存在T
类型的参数。在这种情况下,专业化看起来像:
template <typename T, typename ... ARGS>
struct pack_with_type_impl<true, T, ARGS...> : std::true_type {};
递归
现在是有趣的部分。到目前为止我们没有做过递归,因为上面的情况代表终止条件:参数包为空的一个,所以没有其他事情可做;还有一个匹配的地方,所以没有其他事可做。但现在让我们做一下twe 尚未找到匹配的部分。在这种情况下,我们会有这样的事情:
template <typename T, typename H, typename ... TAIL>
struct pack_with_type_impl<false, T, H, TAIL...> :
pack_with_type_impl<std::is_same<T,H>::value, T, TAIL...> {};
什么?在这种情况下,匹配为false
,因此它将从模板继承,其中提供的参数少一个。也就是说,参数包的头部对应的参数H
已经被剥离,因此它会自行测试,类型T
和TAIL...
会被保留如有必要,以备将来处理。 std::is_same
功能只检查类型是否相同。继承链不断前进,每次都剥离头,直到达到一个终止条件。
例如,假设我想检查参数包是否具有类型int
,并且我提供的参数是:char,double,float
。继承链看起来像这样:
pack_with_type_impl<false,int,char,double,float>
==> pack_with_type_impl<false,int,double,float>
==> pack_with_type_impl<false,int,float>
==> pack_with_type_impl<false,int> # and since the parameter pack is empty now...
==> std::false_type
另一方面,如果我提供了参数char,int,double
,那么继承链将是:
pack_with_type_impl<false,int,char,int,float>
==> pack_with_type_impl<false,int,int,float>
==> pack_with_type_impl<true,int,float> # and since the first argument is true
==> std::true_type
<强>评论强>
有一些更优雅的方法可以做到这一点。对于初学者,我可能会创建一些别名模板:
template <typename ... ARGS>
using has_int = pack_with_type_impl<false,int,ARGS...>;
以便我可以致电:
template <typename ... ARGS>
void increment_int(std::tuple<ARGS...>& tup) {
static_assert(has_int<ARGS...>::value,
"Can only call increment_int on tuples with an int type!");
...
}
但这是解释这个棘手问题的第一次尝试。
答案 1 :(得分:1)
在通用代码中,您无法创建具有N个直接成员的类。但是,您可以创建一个包含一个直接成员和N-1
个继承成员的类,通过专门为N==1
结束递归。
示例:假设您要创建一个类,Foo<int, double, std::string>
包含3个成员函数void Foo::f(int); void Foo::f(int); void Foo::f(std::string)
。这是不可能的,因为N = 3。但是,您可以从Foo<int, double, std::string>
派生Foo<double, std::string>
并添加一名成员void f(int)
。
template<typename HEAD, typename... Tail>
class Foo : public Foo<Tail...>
{
public: void f(HEAD h) { std::cout << h; }
};
template<typename HEAD>
class Foo<HEAD>
{
public: void f(HEAD h) { std::cout << h; }
};