我已经看到一些c++使用模板模板参数(即将模板作为参数的模板)来进行基于策略的类设计的示例。这种技术还有什么其他用途?


当前回答

我将它用于版本控制类型。

如果你有一个通过模板控制的类型,比如MyType<version>,你可以写一个函数来捕获版本号:

template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
    assert(Version > 2 && "Versions older than 2 are no longer handled");
    ...
    switch (Version)
    {
    ...
    }
}

所以你可以根据传入类型的版本做不同的事情,而不是为每个类型重载。 您还可以使用转换函数,以通用的方式接受MyType<Version>并返回MyType<Version+1>,甚至递归它们以具有ToNewest()函数,该函数从任何旧版本中返回类型的最新版本(对于可能已经存储了一段时间但需要使用今天的最新工具进行处理的日志非常有用)。

其他回答

这是我遇到的情况:

template<class A>
class B
{
  A& a;
};

template<class B>
class A
{
  B b;
};

class AInstance : A<B<A<B<A<B<A<B<... (oh oh)>>>>>>>>
{

};

可解为:

template<class A>
class B
{
  A& a;
};

template< template<class> class B>
class A
{
  B<A> b;
};

class AInstance : A<B> //happy
{

};

或者(工作代码):

template<class A>
class B
{
public:
    A* a;
    int GetInt() { return a->dummy; }
};

template< template<class> class B>
class A
{
public:
    A() : dummy(3) { b.a = this; }
    B<A> b;
    int dummy;
};

class AInstance : public A<B> //happy
{
public:
    void Print() { std::cout << b.GetInt(); }
};

int main()
{
    std::cout << "hello";
    AInstance test;
    test.Print();
}

假设您正在使用CRTP为一组子模板提供一个“接口”;父类和子类在其他模板参数中都是参数:

template <typename DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived<int>, int> derived_t;

注意'int'的重复,这实际上是指定给两个模板的相同类型参数。你可以为DERIVED使用模板模板来避免重复:

template <template <typename> class DERIVED, typename VALUE> class interface {
    void do_something(VALUE v) {
        static_cast<DERIVED<VALUE>*>(this)->do_something(v);
    }
};

template <typename VALUE> class derived : public interface<derived, VALUE> {
    void do_something(VALUE v) { ... }
};

typedef interface<derived, int> derived_t;

请注意,您取消了直接向派生模板提供其他模板参数;“接口”仍然接收它们。

这还允许您在依赖于类型参数的“接口”中构建类型defs,可以从派生的模板中访问。

上面的typedef不起作用,因为你不能对一个未指定的模板进行typedef。然而,这是可行的(c++ 11对模板类型defs有原生支持):

template <typename VALUE>
struct derived_interface_type {
    typedef typename interface<derived, VALUE> type;
};

typedef typename derived_interface_type<int>::type derived_t;

不幸的是,派生模板的每个实例化都需要一个derived_interface_type,除非我还没有学到其他技巧。

它提高了代码的可读性,提供了额外的类型安全性,并节省了一些编译器的工作。

假设你想打印容器的每个元素,你可以使用下面的不带template参数的代码

template <typename T> void print_container(const T& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

或带模板模板参数

template< template<typename, typename> class ContainerType, typename ValueType, typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c)
{
    for (const auto& v : c)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

假设传入一个整数,比如print_container(3)。对于前一种情况,模板将由编译器实例化,编译器将抱怨在For循环中使用c,后者将根本不实例化模板,因为没有找到匹配的类型。

一般来说,如果你的模板类/函数被设计成将模板类作为模板形参处理,最好把它弄清楚。

我将它用于版本控制类型。

如果你有一个通过模板控制的类型,比如MyType<version>,你可以写一个函数来捕获版本号:

template<template<uint8_t> T, uint8_t Version>
Foo(const T<Version>& obj)
{
    assert(Version > 2 && "Versions older than 2 are no longer handled");
    ...
    switch (Version)
    {
    ...
    }
}

所以你可以根据传入类型的版本做不同的事情,而不是为每个类型重载。 您还可以使用转换函数,以通用的方式接受MyType<Version>并返回MyType<Version+1>,甚至递归它们以具有ToNewest()函数,该函数从任何旧版本中返回类型的最新版本(对于可能已经存储了一段时间但需要使用今天的最新工具进行处理的日志非常有用)。

下面是一个简单的例子,摘自Andrei Alexandrescu的《现代c++设计-泛型编程和设计模式的应用》:

他使用一个带有模板参数的类来实现策略模式:

// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
   ...
};

他解释说: 通常,宿主类已经知道,或者可以很容易地推断出策略类的模板参数。在上面的例子中,WidgetManager总是管理Widget类型的对象,因此要求用户在CreationPolicy的实例化中再次指定Widget是多余的,而且有潜在的危险。在这种情况下,库代码可以使用模板模板参数来指定策略。

结果是客户端代码可以以一种更优雅的方式使用'WidgetManager':

typedef WidgetManager<MyCreationPolicy> MyWidgetMgr;

而不是更麻烦,更容易出错的方式,一个定义缺乏模板模板参数将需要:

typedef WidgetManager< MyCreationPolicy<Widget> > MyWidgetMgr;