Gearoid Murphy
Gearoid Murphy

Reputation: 12116

How to detect template parameters of base classes at compile time (for errors)?

I've been using the Curiously recurring template pattern The general code looks like this:

template <typename T> void genericFunction(T &);
template <typename T> struct Functionality {
    void genericMethod() {
        genericFunction(*((T *)this)) ;
    }
};

struct Klass : public Functionality<Klass> {};

void main() {
    Klass obj ;
    obj.genericMethod();
}

template <> void genericFunction<Klass>(Klass &obj) {
    //do stuff with Klass &obj here
}

I ran into an error today which cost me about 90 minutes of hair-pulling fustration, this error was caused by using an incorrect template parameter for my base class inheritance declaration, somewhat like so:

struct Klass : public Functionality<SomeOtherKlass> {}; //SomeOtherKlass wrong!!!

I'd like to enhance my code so that this mismatch between the derived class and the base class template parameter is detected (runtime, compile time, anytime :) ), is this even possible?, thanks.

Upvotes: 7

Views: 630

Answers (5)

Georg Fritzsche
Georg Fritzsche

Reputation: 98964

You could assert the relation in e.g. genericMethod() using Boost or C++11 features:

BOOST_STATIC_ASSERT(( boost::is_base_of<Functionality<T>, T>::value ));

... although that is assuming that the other class doesn't derive from Functionality<T> as well.

An alternative could be to assert the relation at runtime in test-builds:

template <typename T> struct Functionality {
#ifdef TEST_BUILD
    virtual ~Functionality() {}
#endif
    void genericMethod() {
#ifdef TEST_BUILD
        assert(dynamic_cast<T*>(this));
#endif
        genericFunction(*((T *)this)) ;
    }
};

Note that the test won't work inside constructors and destructors

Upvotes: 3

celtschk
celtschk

Reputation: 19721

In C++11, the following should work:

template<typename T> class Base
{
  friend T; // allowed in C++11
private:
  ~Base() {}
public:
  // ...
};

class Derived: public Base<Derived> {}; // OK

class WronglyDerived: public Base<Derived> {}; // Error: destructor of base class is private

Upvotes: 2

Gearoid Murphy
Gearoid Murphy

Reputation: 12116

The most tangible suggestion thus far is to use dynamic_cast to expose malformed inheritance declarations in the Base class constructor, like so:

#include <iostream>
template <typename T> struct Base {
    Base() {
        std::cout<<dynamic_cast<T *> (this)<<std::endl;
    }
    virtual void iampolymorphic(){}
};
struct Decoy {} ;
struct Pass : public Base<Pass>{}; //correct
struct Fail : public Base<Decoy>{}; //incorrect
int main() {
    Pass p ;
    Fail f ;
    return 1 ;
}

This code compiles on g++ 4.6.1, Amd64 Xubuntu 11.10. The output for both dynamic cast operations is a null pointer. Comments, criticisms and observations are welcomed.

Upvotes: -1

Alan Stokes
Alan Stokes

Reputation: 18954

Suppose you add a templated constructor to the base that takes a pointer to arbitrary type;

template<class U> Functionality(U *) { ... }

Then each derived class's constructor can pass its this pointer to the constructor, and in the body of the constructor you just static assert that U and T are the same type.

The constructor parameter is never actually used so should be optimised out entirely. And if this is the only base class constructor you can't forget to call it. The only problem would be if you passed something other than this.

Upvotes: 0

Alan Stokes
Alan Stokes

Reputation: 18954

You could use a dynamic_cast, which will return null if you have the wrong parameter type. (You'll need at least one virtual function in the base for this to work - the destructor, say.)

If you're worried about efficiency, boost has a polymorphic_cast which does a dynamic cast in debug mode but a static cast for production.

(And in any case it would be nice to avoid the use of the C-style cast.)

Upvotes: 1

Related Questions