如何在类型依赖于派生 class 的基本模板 class 中声明成员?

How to declare a member in a base template class where the type is dependent of the derived class?

给定一个使用 CRTP 的基础 class,我正在考虑在基础模板 class 中声明一个成员,其中类型依赖于派生的 class。

虽然以下内容按预期工作:

template <class T> class BaseTraits;
template <class T> class Base {
    using TypeId = typename BaseTraits<T>::TypeId;
    TypeId id;
 public:
    Base() { id = 123; }
    TypeId getId() { return id; }
};

class Derived;
template <> class BaseTraits<Derived> {
public:
    using TypeId = int;
};

class Derived : public Base<Derived> {};

int main(int argc, char ** argv) {
     Derived foo;
     return foo.getId();
}

我想知道我是否可以简化实现。我可以将第二个模板参数添加到 Base 模板,并使 BaseTraits 更简单甚至摆脱它。然而,上面的代码片段已经尝试删除第二个模板参数。我正在寻找不涉及 Base.

的第二个模板参数的解决方案

我试过类似下面的方法,但它没有编译:

error: invalid use of incomplete type 'class Derived'

template <class T> class Base {
    using TypeId = typename T::TypeId;
    TypeId id;
 public:
    Base() { id = 123; }
    TypeId getId() { return id; }
};

class Derived : public Base<Derived> {
public:
    using TypeId = int;
};

int main(int argc, char ** argv) {
     Derived foo;
     return foo.getId();
}

更新:

这是一种简化,但你为此付出了一些代价。

#include <any>

template <class T> class Base {
    std::any id; // expensive, but cannot have T::TypeId here
 public:
    Base() : id(123) {}
    auto getId() { 
         return std::any_cast<typename T::TypeId>(id); 
    } // T::TypeId is OK inside a member function
};

class Derived : public Base<Derived> {
public:
    using TypeId = int;
};

像这样的事情怎么样:

template <typename T, typename TypeId> class Base 
{
private:
    TypeId id;
public:
    Base() { id = 123; }
    TypeId getId() {return id;}
};

class Derived : public Base<Derived, int> {};

为什么不颠倒 class 层次结构?

template <class T>
class Base : T {
    using TypeId = typename T::TypeId;
    TypeId id;
 public:
    Base() { id = 123; }
    TypeId getId() { return id; }
};

struct BasicDerived {
    using TypeId = int;
};


using Derived = Base<BasicDerived>;

老实说,您遇到了硬循环依赖的瓶颈。 任何出路都是臭的。
两个模板参数最终似乎是一个很小的代价。

您能否声明一个采用 Derived 和 TypeID 的虚拟模板 class?不过,我认为这对您没有任何好处。

TypeID:Derived 是 1:1 映射吗?使用另一个辅助模板过度表示 1:1 映射到从 TypeID 派生的反向查找会感觉更好吗?请注意,需要在 Derived class 之外定义 TypeID 才能执行此操作。
TypeID 真的需要在 class 中定义吗?它可以提取 Base 中传入的定义以支持内部 typedef 的现有使用吗?

你能双包含吗?拆分或宏化您的派生定义,以便 typeid 位于可以包含在模板之前的基本 class 定义中?这个 DerivedBase 可以在命名空间中声明并包含一个 typedef link 回到完整的 Derived class 所以 Base 可以找到它以供参考。

其实我还想多了...这不是太不愉快:
你可以有一个绑定结构,甚至可以写成一个宏,就在真正的 class.
之前声明 绑定结构将枚举和不完整的类型定义定义为真实的 class.
模板是在所有这些之前定义的,但是使用 typename 来延迟其依赖性,但它是由真实的 class 实例化的,并且仅依赖于绑定结构

template <class ThatClassWrapper>
class MyBase
{
protected:
    typedef typename ThatClassWrapper::TypeId TypeId;
    typedef typename ThatClassWrapper::RealClass ThatClass;
    TypeId typeIdValue;
    TypeId  GetTypeId() {   return typeIdValue; }
    std::vector<ThatClass*> storage;
};

class SomeClass;
namespace TypeIdBinding
{
    struct SomeClass
    {
        enum TypeId
        {
            hello, world
        };
        typedef ::SomeClass RealClass;
    };
}
class SomeClass: public MyBase<TypeIdBinding::SomeClass>
{
public:
    bool CheckValue(TypeId id)
    {   return id == typeIdValue;   }
};

请注意,真正的 class 是使用模板库中定义的 TypeId,命名成员不直接可见。您可以通过让模板 Base 从绑定结构派生来解决这个问题(确认它以这种方式编译)。尽管我实际上喜欢在 c++11 中,您可以仅从另一个命名空间导出或 typedef 枚举类型名称,并将该类型名称用作枚举成员的前缀,有助于避免名称污染。

是否可以使成员类型直接依赖于派生的class?以auto声明的成员函数的结果类型(推导出return类型),不可能。

因此,像您在您的解决方案中所做的那样使用类型特征是最好且唯一的解决方案

原因是当定义派生class时,基class必须是一个完整类型:编译器在解析之前必须首先实例化并解析基class定义派生的 class 定义,C++ standard N4140 [derived.class]/2(粗体是我的):

The type denoted by a base-type-specifier shall be a class type that is not an incompletely defined class;[...]