5

Given a base class using CRTP, I'm looking at declaring a member in the base template class where the type is dependent of the derived class.

While the following works as intended:

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();
}

I wonder if I could simplify the implementation. I could add a second template parameter to the Base template, and make BaseTraits simpler or even get rid of it. However the above snippet is already an attempt to remove the second template parameter. I'm looking at solutions that doesn't involve a second template parameter for Base.

I've tried something like the following but it doesn't compile:

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();
}

UPDATE:

  • I'm limited to c++14.
  • Base must be a template.
  • Performance is a must.
4

6 回答 6

4

是否可以使成员类型直接依赖于派生类?auto将使用(推导的返回类型)声明的成员函数的结果类型分开,这是不可能的。

因此,在解决方案中使用类型特征是最好的也是唯一的解决方案

原因是定义派生类时基类必须是完整类型:编译器必须先实例化并解析基类定义,然后再解析派生类定义,C++标准N4140 [derived.class]/2(粗体)是我的):

由基本类型说明符表示的类型应该是一个不是不完整定义的类的类类型;[...]

于 2017-12-12T14:15:34.077 回答
2

像这样的东西怎么样:

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

class Derived : public Base<Derived, int> {};
于 2017-12-10T21:29:23.387 回答
1

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

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>;
于 2017-12-10T21:33:31.093 回答
1

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

#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;
};
于 2017-12-10T21:12:39.470 回答
1

实际上,我想了更多......这并不太令人不快:
你可以有一个绑定结构,甚至可以写成一个宏,在真正的类之前声明。
绑定结构为真实类定义了枚举和不完整的 typedef。
模板是在所有这些之前定义的,但是使用 typename 来延迟它的依赖,但它是由真实类实例化的,并且只依赖于绑定结构

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;   }
};

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

于 2017-12-11T19:01:27.657 回答
0

老实说,你已经碰到了硬循环依赖的墙。任何出路都会很臭。
最终,两个模板参数似乎是一个很小的代价。

你能声明一个接受 Derived 和 TypeID 的虚拟模板类吗?不过,我认为这对你没有任何好处。

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

你能双重包括吗?拆分或修改派生的定义,以便 typeid 位于可以包含在模板之前的基类定义中?这个 DerivedBase 可以在命名空间中声明,并包含指向完整 Derived 类的 typedef 链接,因此 Base 可以找到它以供参考。

于 2017-12-11T11:23:03.983 回答