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

时间:2017-12-10 20:52:15

标签: c++ c++11 templates crtp abstract-base-class

给定一个使用CRTP的基类,我正在寻找在基本模板类中声明一个类型依赖于派生类的成员。

虽然以下工作符合预期:

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的第二个模板参数的解决方案。

我尝试了类似下面的内容,但它没有编译:

  

错误:无效使用不完整类型'类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();
}

更新

  • 我只限于c ++ 14。
  • Base必须是模板。
  • 表现是必须的。

6 个答案:

答案 0 :(得分:3)

是否可以使成员类型直接依赖于派生类?将appart的成员函数的结果类型设为auto(推导出的返回类型),是不可能的。

因此,您的解决方案中使用 type-trait 是最佳且唯一的解决方案

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

  

由base-type-specifier表示的类型应为不是未完全定义的类的类类型; [...]

答案 1 :(得分:2)

这样的事情:

<custom-element></custom-element>
<fake-element></fake-element>
<p></p>
<font-face></font-face>

答案 2 :(得分: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;
};

答案 3 :(得分: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>;

答案 4 :(得分:0)

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

你可以声明一个带有Derived和TypeID的虚拟模板类吗?不过,我认为它不会让你获益。

TypeID:派生1:1映射?将另一个帮助器模板的1:1映射过度表示为从TypeID派生的后备查找会感觉更好吗?请注意,需要在Derived类之外定义TypeID才能执行此操作 TypeID真的需要在类中定义吗?它可以从Base中取出传入的定义来支持内部typedef的现有用法吗?

你可以双重包括吗?拆分或编写您的derived定义,以便typeid在基类定义中,可以包含在模板之前?这个DerivedBase可以在命名空间中声明,并包含一个返回完整Derived类的typedef链接,因此Base可以找到它以供参考。

答案 5 :(得分:0)

实际上,我想了一些......这不太令人不愉快:
你可以有一个绑定结构,甚至可以写成宏,在真正的类之前声明 绑定结构定义枚举,并为实际类定义不完整的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中,你可以从另一个命名空间中导出或者只输入enum typename,并使用该类型名作为枚举成员的前缀,有助于避免名称污染。