How to avoid errors while using CRTP?
Asked Answered
M

5

16

Using CRTP sometimes I write a code like this:

// this was written first
struct Foo : Base<Foo, ...>
{
   ...
};

// this was copy-pasted from Foo some days later
struct Bar : Base<Foo, ...>
{
   ...
};

And it's very difficult to understand what goes wrong, until I trace code in debugger and see that Bar's members aren't used in Base.

How to reveal this error at compile time?

(I use MSVC2010, so I can use some C++0x features and MSVC language extensions)

Monograph answered 11/12, 2010 at 16:54 Comment(0)
M
17

In C++0x you have a simple solution. I don't know whether it is implemented in MSVC10 however.

template <typename T>
struct base
{
private:
    ~base() {}
    friend T;
};

// Doesn't compile (base class destructor is private)
struct foo : base<bar> { ... };
Munguia answered 30/8, 2011 at 7:35 Comment(5)
actually it doesn't work if the dtor is never called.Monograph
@Abyx: Interestingly with gcc 4.9, if I use placement new to construct an object of type struct S : base<T> {}, it complains about the constructor S::S () being implicitly deleted due to ~base being private. However in this case the destructor is never called.Munguia
More interestingly, gcc 4.8.1 does not complain at all!Munguia
placement new will call base dtor on exceptionMonograph
@Abyx: I realize that, this is why the class is not instantiable (similar problem occurs with template constructors for class that have eg. a unique_ptr to an incomplete type), and eventually why my solution prevents S to inherit base<T>. However, gcc 4.8 does not complain: this is either a bug or the default constructor may be allowed to be proven noexcept, but a further derived class with throwing ctor would have to know how to call derived dtor. Anyway, this is far from obvious without doing some standard exegesis. My belief is that gcc 4.8 is wrong and 4.9 is right here however.Munguia
M
11

You can use something like this:

template<class T> class Base {
protected:
   // derived classes must call this constructor
   Base(T *self) { }
};

class Foo : public Base<Foo> {
public:
   // OK: Foo derives from Base<Foo>
   Foo() : Base<Foo>(this) { }
};

class Moo : public Base<Foo> {
public:
   // error: constructor doesn't accept Moo*
   Moo() : Base<Foo>(this) { }
};

class Bar : public Base<Foo> {
public:
   // error: type 'Base<Bar>' is not a direct base of 'Bar'
   Bar() : Base<Bar>(this) { }
};
Matrices answered 11/12, 2010 at 17:42 Comment(4)
It gets really verbose when Foo is itself a template.Munguia
@Alexandre: templates are verbose.Matrices
yes they are, but in production code your code gets harder to use than the plain CRTP (I tried once to use something along these lines for the same reason as OP).Munguia
This solution is much cleaner than the private destructor trick -- for that it is: a trick that comes with its own problems, see also hereDanseur
M
2
template<typename T, int arg1, int arg2>
struct Base
{
    typedef T derived_t;
};

struct Foo : Base<Foo, 1, 2>
{
    void check_base() { Base::derived_t(*this); } // OK
};

struct Bar : Base<Foo, 1, 2>
{
    void check_base() { Base::derived_t(*this); } // error
};

This code is based on Amnon's answer, but checking code don't contains name of derived class, so I can copy and paste it without changes.

Monograph answered 12/12, 2010 at 19:40 Comment(0)
P
0

There's no way of knowing the deriving type. You could enforce that Foo derived from Base<Foo>, but you can't enforce that no other classes also derive from that.

Palter answered 11/12, 2010 at 16:57 Comment(0)
M
0

I can use a macro

#define SOMENAMESPACE_BASE(type, arg1, arg2) type : Base<type, arg1, arg2>

but I don't want to use macros if better solution exists.

Monograph answered 11/12, 2010 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.