目前的问题是嵌套模板的实例化需要完整类型的封闭类和模板B的声明:
template <typename TChild>
struct Base {
// TChild should be complete at the moment of this declaration
// template B should be declared at this moment.
template <typename T>
using Foo = typename TChild::template B<T>;
};
Base的实例化
struct Child : Base<Child>
/* TChild = Child at this moment is incomplete */
{
template <typename T>
using B = T;
/* Point where template B begins to exist */
}; /* point where Child is complete */
这些规则是语言设计的,它们的目标是避免强制编译器在代码中多次往返,可能是无限递归,以实际实例化你的意思。弱类型的解释器语言通常没有这样的问题,因为它们可以在以后“纠正”自己。
案例 1。
静态函数解决方案之所以有效,是因为没有进行类型声明。您已经声明了一个实际上具有全局范围的函数模板,但尚未创建具体的函数或类型。
struct Base {
template <typename T>
static auto constexpr getFoo() {
return typename TChild::template B<T>{};
}
};
struct Child : Base<Child> {
template <typename T>
using B = T;
/* At this point we can instantiate Child::getFoo<int>()*/
}; /* Child is complete now */
此时Child::getFoo<T> 的实例化是可能的,但它只需要函数的返回类型。
using Bar = decltype(Child::getFoo<int>());
您可以在声明B 之后将此声明放入Child,因为此时B<int> 将完成。你还是不能在Base声明它
案例 2。
您的解决方案声明了另一个模板 Foo,它不会在 Base 中实例化它。此模板不明确依赖于TChild,但需要foo() 的原型才能存在于实例化点。
template <typename TChild>
struct Base {
template <typename T>
static auto foo(){
return typename TChild::template B<T>();
}
// Foo is a template
template <typename T>
using Foo = std::decay_t<decltype(foo<T>())>;
};
实例化发生在您将使用Base::Foo<T> 的地方,而实际上您并没有。该声明在您的解决方案中是无效的。在B 声明之后使用它是合法的。您不能在 Base 内或声明 B 之前的任何地方使用它。
现在如果您实际上需要在Base 中使用B 的实例怎么办?特质类解决方案来了:
案例 3。
特征可以是专门用于子类或具体类的模板,这是一种设计选择。特征作为 CRTP 基类的基类,是一种混合形式。它的作用是为 CRTP 提供有用的声明。最灵活的特征命名的可能解决方案之一:
template <typename TChild, template<class> typename Trait>
struct Base : public Trait<TChild> {
// Trait<TChild>:: tells compiler that Foo is dependant on TChild
// and is declared in base class Trait. As compiler had reached this
// point, the substitution was successful and thus Trait is complete
using Foo = typename Trait<TChild>::template B<int>;
// Foo is assumed to be a complete type, we can use it here!
Foo make_foo() { return Foo{}; }
};
// Declaring trait template in this case.
template <typename T> struct ChildTrait;
// And specializing
template <>
struct ChildTrait<struct Child> {
template <typename T>
using B = T;
};
struct Child : Base<Child,ChildTrait> {
using Bar = typename Base::Foo;
};
static_assert(std::is_same<Child::B<int>,int>::value,"");
static_assert(std::is_same<Child::B<std::string>,std::string>::value,"");
这里的想法是Trait<TChild> = ChildTrait<Child> 必须是并且可以是Base 中的一个完整类,否则我们无法从中派生Base。稍作修改(省略 using、static_assert、typename 使用)这将在 C++98 中编译,因为它不需要 decltype。此方法由标准组件的某些实现使用,例如std:: streams.
特征可以描述具体的存储类型、分配器等。重要的是生成的具体类型没有关系,但具有在Base 中声明的共享接口。