C++ CRTP and accessing derived's nested typede

2020-05-25 17:57发布

问题:

edit: I'll put a github link here when I am done altering my design for anyone who is interested.

Background

I'm replacing a boost::intrusive, intrusive_set, with my own implementation as 64-bit compiled intrusive-set stuffs 3 x 8-byte pointers into my container nodes. my container has a limit of 2^16 nodes so I can bring it down to 4-bytes per node with 2x 16-bit offset ordinals (which is a 6x reduction of size).

In the example below base is the intrusive-set container. The derived class has a std::vector<container_entry_type<entry_type> >. obviously with this level of indirection I need to have a bunch of nested typedef's in derived, which I'd like to refer to in base.

p.s., the containers are for the AST of data description language. The contained elements are therefore small data types and 3 x 8-bytes is very significant. Especially so since the containers are used to validate data-sets in tight loops.

The problem isolated

I want to achieve the following semantics:

template<typename TWO>
class base
{
public:
  void foo(typename TWO::dummy & d);
};

template<typename DUMMY>
class derived
  : private base< derived<DUMMY> >
{
public:
  typedef DUMMY dummy;
};

struct tag{};

int main()
{
  derived<tag> foo;
}

But I can't access the nested typedef from the base. This is what clang has to say about the matter:

main.cc: In instantiation of ‘base<derived<tag> >’:
main.cc:9:7:   instantiated from ‘derived<tag>’
main.cc:20:16:   instantiated from here
main.cc:5:8: error: no type named ‘dummy’ in ‘class derived<tag>’

Instead I am having to do:

template<typename type_key>
class traits
{
public:
  typedef type_key dummy;
};

template<typename TWO, typename type_key>
class base
{ 
public:
  void foo(typename traits<type_key>::dummy & d);
};

template<typename DUMMY>
class derived
  : private base< derived<DUMMY>, DUMMY >
{
public:
  typedef DUMMY dummy;
};

struct tag{};

int main()
{
  derived<tag> foo;
}

Is this the only way to achieve my use-case ? it just makes things a whole lot more verbose. I suppose derived could also derive from traits to save some keystrokes.

Another choice is to not use derivation and to wire the logic straight into what is currently derived. However, I'd like to individually unit test base.

回答1:

Another possibility (that might or might not save you keystrokes) would be not using the derived classes' nested types in the parent in some places. Eg. instead of

void foo(typename TWO::dummy & d);

you'd use

template <class T>
void foo(typename T& d);

For extra points, you could use SFINAE to actually limit T to the types permissible for the original variant. (Note that inside nested templates, TWO::dummy can be used freely - they are only instantiated after the whole thing incl. derived has been, so it works out. In the naive version, derived is still incomplete at the point of instantiating the base with its member functions, it has no ::dummy, which is why it fails)



回答2:

Extending @jpalecek's idea, we could make that template argument take a default argument. But you need to enable C++0x to get this

#include <typeinfo>
#include <cstdio>

template<typename TWO>
class base
{
public:
    template <typename X = TWO>   // <-- (requires C++0x to have a default)
    void foo(typename X::dummy& d)
    {
        printf("%s\n", typeid(d).name());
    }
};

template<typename DUMMY>
class derived
  : public base< derived<DUMMY> >
{
public:
  typedef DUMMY dummy;
};

struct tag{};

int main()
{
  derived<tag> foo;
  tag t;
  foo.foo(t);       // <--- call the function like normal.
}

http://ideone.com/AXXdW



回答3:

There's no need for the traits class. You can just use type_key directly in base.

You cannot, however, avoid passing the type explicitly to base. At the time base is instantiated, the typedef in derived has not yet been seen by the compiler (more exactly: the class derived is not yet complete — how could it, given that even its base class doesn't exist yet).