请考虑以下示例:
template <typename Consumer>
class ClassA
{
public:
template<class... Args>
explicit ClassA(Args &&... args) :
consumer_(std::forward<Args>(args)...)
{
}
void consume()
{
consumer_.consume();
}
private:
Consumer consumer_;
};
template <typename Consumer>
class ClassB
{
public:
template<class... Args>
explicit ClassB(Args &&... args) :
consumer_(std::forward<Args>(args)...)
{
}
void consume()
{
consumer_.consume();
}
private:
Consumer consumer_;
};
class ClassC
{
public:
explicit ClassC(int val) :
val_(val)
{
}
void consume()
{
std::cout << "ok " << val_ << std::endl;
}
private:
int val_;
};
void usage()
{
ClassA<ClassB<ClassC>> composed_object(3);
composed_object.consume();
}
它是一种模板桥(或代理?或策略?)模式,我可以在编译时轻松地编写和更改实现。
由于性能原因,我尽量避免使用动态多态性。
那么,问题是:如何允许ClassB调用一些ClassA方法?
我想到的第一件事是将ClassA的引用传递给ClassB。但是有一个模板化的类链,我不想改变类链的用法。
我可以重写ClassB,如下所示:
template <typename Interface, typename Consumer>
class ClassB
{
public:
template<class... Args>
explicit ClassB(Interface &interface, Args &&... args) :
consumer_(std::forward<Args>(args)...),
interface_(interface)
{
}
void consume()
{
consumer_.consume();
}
private:
Consumer consumer_;
Interface &interface_;
};
因此,它现在需要一个额外的父类接口的模板化参数和一个对构造函数中的父级的引用。
但是我不知道如何在ClassA中指定模板化的param接口而不改变链的用法。
在我看来,这是一种模板参数无穷大循环。
您希望将生产者传递给ClassB而不传递它。我们可以通过CRTP实现这一点。
template <typename Consumer>
class ClassA:private Consumer
{
public:
template<class... Args>
explicit ClassA(Args &&... args) :
Consumer(std::forward<Args>(args)...)
{
}
void say_hello() { std::cout << "hello!\n"; }
void consume()
{
Consumer::consume();
}
};
并类似地更改classB
。一切都继续按原样工作。现在您希望classB
找到它的生产者。
template <template<class>class Producer, typename Consumer>
class ClassB:private Consumer
{
public:
template<class... Args>
explicit ClassB(Args &&... args) :
Consumer(std::forward<Args>(args)...)
{
}
void consume()
{
GetProducer().say_hello();
Consumer::consume();
}
private:
Producer<ClassB>& GetProducer() { return *static_cast<Producer<ClassB>*>(this); }
Producer<ClassB> const& GetProducer() const { return *static_cast<Producer<ClassB> const*>(this); }
};
这里我们告诉classB
谁为它生成,并添加一个获得它们的生产者的私有方法(假设它们的生产者使用相同的继承组合策略)。
void usage()
{
ClassA<ClassB<ClassA, ClassC>> composed_object(3);
composed_object.consume();
}
这里,我们介绍classB
如何生成它们。
构造的类知道一些事情。它知道它的构造参数,并且它还有一个隐式的this
指针。
在这里,我们通过CRTP和继承将生产者标识插入类中,而不必将其作为参数传递。
使用模板别名,您甚至可以“跳过”级别。但是,如果你搞错了,那么UB很快就会产生效果。
template<template<class...>class Z, class...Us>
struct ztemplate {
template<class...Ts>
using result=Z<Us..., Ts...>;
};
template<class...> struct empty_t {};
template<class zProducer, class zConsumer, class...Ts>
class ClassA:
public zConsumer::template result< ztemplate<ClassA, zProducer>, Ts...>
{
public:
using Consumer = typename zConsumer::template result< ztemplate<ClassA, zProducer>, Ts... >;
using Producer = typename zProducer::template result< ztemplate<ClassA>, zConsumer, Ts... >;
template<class... Args>
explicit ClassA(Args &&... args) :
Consumer(std::forward<Args>(args)...)
{
}
void say_hello() { std::cout << "hello!\n"; }
void consume()
{
static_assert( std::is_same_v< typename Consumer::Producer, ClassA > );
Consumer::consume();
}
};
template <class zProducer, class zConsumer, class...Ts>
class ClassB:
public zConsumer::template result< ztemplate<ClassB, zProducer>, Ts...>
{
public:
using Consumer = typename zConsumer::template result< ztemplate<ClassB, zProducer>, Ts... >;
using Producer = typename zProducer::template result< ztemplate<ClassB>, zConsumer, Ts... >;
template<class... Args>
explicit ClassB(Args &&... args) :
Consumer(std::forward<Args>(args)...)
{
}
void consume()
{
static_assert( std::is_same_v< typename Producer::Consumer, ClassB > );
GetProducer().say_hello();
Consumer::consume();
}
private:
Producer& GetProducer() { return *static_cast<Producer*>(this); }
Producer const& GetProducer() const { return *static_cast<Producer const*>(this); }
};
template<class...>
class ClassC
{
public:
explicit ClassC(int val) :
val_(val)
{
}
void consume()
{
std::cout << "ok " << val_ << std::endl;
}
private:
int val_;
};
void usage()
{
ClassA<ztemplate<empty_t>, ztemplate<ClassB>, ztemplate<ClassC>> composed_object(3);
composed_object.consume();
}
但这可能太过分了。
这里链中的每个类都被“自动”地告知整个链的结构。(静态断言不是通用的,只是debug通过not-UB检查该特定实例中的工作情况)。
为了减少编译时的膨胀,可以更改传入的classc
,如下所示:
template<class Z> struct zAlways {
template<class...>using result = Z;
};
void usage()
{
ClassA<zAlways<empty_t>, ztemplate<ClassB>, zAlways<ClassC>> composed_object(3);
composed_object.consume();
}
从classc
中删除模板
ZTemplate
是将模板作为类传递的模式。我们在这里使用它是因为您不能编写一个以第一个参数为模板、一个以第一个参数为模板、一个以第一个参数为模板、一个以...
zAlways
遵循模式,即真实模板在foo::template result
中。这里我们创建一个ZTemplate
,它总是返回一个特定的类。
但我离题了。