6

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.
Flyer
  • 327
  • 2
  • 14
  • Well your second example doesn't work because you can't use the class as template parameter before it was even fully declared. What do you want to achieve with those classe? Are you sure that they need to be templated at all? – Maroš Beťko Dec 10 '17 at 20:59
  • @MarošBeťko You very much can, it's an incomplete class at this point and you can use an incomplete class as a template parameter. – n. m. could be an AI Dec 10 '17 at 21:14
  • @MarošBeťko I'd like Base to declare a member that only Derived know the type. Yes I need to use templates, at least for Base. – Flyer Dec 10 '17 at 21:25
  • Do you really need `id` to be a member of `Base`? You could make it a member of `Derived` just as well... – n. m. could be an AI Dec 10 '17 at 21:57
  • Unfortunately yes. In fact I need an array of those. – Flyer Dec 10 '17 at 22:03
  • It isn't clear why you strictly need it there. You can have a function that returns (a reference/pointer to) the id array in Base, while the array itself sits in Derived. – n. m. could be an AI Dec 11 '17 at 14:22

6 Answers6

5

Is it possible to make a member type directly dependent on the derived class? Taking appart the result type of a member function declared with auto (deduced return type), it is not possible.

So the use of a type-trait as you do in your solution is the best and only solution.

The reason is that a base class must be a complete type when the derived class is defined: the compiler must first instantiate and parse the base class definition before it parses the derived class definition, C++ standard N4140 [derived.class]/2 (bold is mine):

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

Oliv
  • 17,610
  • 1
  • 29
  • 72
2

What about something like this:

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

class Derived : public Base<Derived, int> {};
Killzone Kid
  • 6,171
  • 3
  • 17
  • 37
  • Thank you @killzone-kid. Yes it would be simpler however I'm looking at solutions that doesn't involve a second template parameter for Base. In fact this what I have today. My goal is to reduce the number of template parameters for Base to just one. – Flyer Dec 10 '17 at 21:37
  • As you can see first param is not used here, I left it because you had it originally and I thought you might need it later. You can just `template class Base` and `class Derived : public Base {};` if you want to. – Killzone Kid Dec 10 '17 at 21:41
  • I extracted the gist of the real code to focus on the problem. Derived needs to inherit from Base. – Flyer Dec 10 '17 at 21:46
1

This is kind of simplified, but you pay some price for it.

#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;
};
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you @n.m. Your solution is good however I can't use std::any because I'm limited to c++14 in my current environment and this is a performance critical application. – Flyer Dec 10 '17 at 21:22
  • 1
    You can write your own simplified `any` or use `boost::any` (or just use `void*` in a pinch), but performance is going to suffer in any case. – n. m. could be an AI Dec 10 '17 at 21:55
1

Why not reversing the class hierarchy?

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>;
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
1

Actually, I thought some more... this isn't too unpleasant:
You could have a binding struct, could even be written as a macro, declared just before the real class.
The binding struct defines the enum and an incomplete typedef to the real class.
The template is defined before all of that, but uses typename to defer its dependency, but it is instanced by the real class and only dependant on the binding struct

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

Note that the real class is using TypeId as defined in the template base, and the named members are not directly visible. You could fix that by having the template Base derive from the binding struct (confirmed that it compiles that way). though I actually like that in c++11 you can export or typedef just the enum typename from another namespace and use that type name as a prefix for the enum members, helping to avoid name pollution.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • Thank you for this other suggestion. You are very creative :) I I agree with that it isn't too unpleasant but it is not CRTP anymore and it looses the Derived > Base inheritance. As you said it seems like I've hit the wall of circular dependencies and I think @oliv is right. – Flyer Dec 13 '17 at 20:45
0

To be honest you have hit the wall of hard circular dependencies. Any way out will be smelly.
Two template arguments seems like a small price in the end.

Could you declare a dummy template class that takes Derived and TypeID? I don't think it gains you anything, though.

Is TypeID:Derived a 1:1 mapping? Would it feel better to over-represent that 1:1 mapping with another helper template to back-look-up Derived from TypeID? Note that TypeID would need to be defined outside the Derived class to do this.
Does TypeID really need to be defined inside the class? Could it leach off the passed-in definition in Base to support the existing use of the internal typedef?

Can you double-include? Split or macriose your definition of derived so that typeid is in a base class definition that can be included before the template? This DerivedBase could be declared in a namespace and contain a typedef link back to the full Derived class so Base can find it for references.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • Thank you for your suggestion. Indeed I might keep the second template parameter. "Is TypeID:Derived a 1:1 mapping?" Yes "Would it feel better to over-represent that 1:1 mapping with another helper template to back-look-up Derived from TypeID?" Do you mean like the BaseTraits template does ? I don't know. It doesn't seems much simpler than the second template parameter. "Does TypeID really need to be defined inside the class?" No. I don't understand the follow up question. "Double-include or split ?" That's interesting. What do you mean by typedef link back though ? – Flyer Dec 11 '17 at 14:19