继承与参数模板包一起使用。诀窍是什么?

时间:2016-06-13 08:36:46

标签: c++11 variadic-templates

我看过一些代码(https://stackoverflow.com/a/19209751/877329)使用递归"继承"修改模板参数包。这个建筑背后的想法是什么?它是如何工作的?

2 个答案:

答案 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已经被剥离,因此它会自行测试,类型TTAIL...会被保留如有必要,以备将来处理。 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; }
};