Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using child class as a template parameter of a base class and as a nested name specifier

I'm using my class as a template parameter of one of its parent class, and that parent class uses it in a template argument (though sizeof()).

And the compiler gives me:

error : incomplete type 'Invoker::workerClass {aka MyClass}' used in nested name specifier

Yet the class is well defined in the file. I guess this is because the child class isn't instantiated at the moment of the base class' instantiation, yet that kind of thing happens with CRTP and there's no problems.

The reason I use the child class in the template argument is to do a different function call if the child class has, or doesn't have, a specific function.

Here's a minimal code for testing

/* Structure similar to boost's enable if, to use
  SFINAE */
template <int X=0, class U = void>
struct test {
    typedef U type;
};

enum Commands {
    Swim,
    Fly
};

/* Structure used for template overloading,
  as no partial function template specialization available */
template<Commands T>
struct Param {

};

template <class T>
class Invoker
{
public:
    typedef T workerClass;

    workerClass *wc() {
        return static_cast<workerClass*>(this);
    }

    template <Commands command>
    void invoke() {
        invoke2(Param<command>());
    }

    /* If the child class has those functions, call them */
    /* Needs template paramter Y to apply SFINAE */
    template<class Y=int>
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 
    invoke2(Param<Fly>) {
        wc()->fly();
    }

    template<class Y=int>
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::swim))>::type 
    invoke2(Param<Swim>) {
        wc()->shoot();
    }

    template<Commands command>
    void invoke2(Param<command>) {
        /* Default action */
        printf("Default handler for command %d\n", command);
    }
};

template <class T, class Inv = Invoker<T> >
class BaseClass : public Inv
{
public:
    template<Commands command>
    void invoke() {
        Inv::template invoke<command>();
    }
};

class MyClass : public BaseClass<MyClass>
{
public:
    void swim() {
        printf("Swimming like a fish!\n");
    }

    /* void fly(); */
};


void testing() {
    MyClass foo;
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */
    foo.invoke<Swim>(); /* Should print the swimming message */
}

The error happens at the line:

typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 

So, is there any compiler that supports this, or is this explicitely specified by the standard as an invalid use of templates? Do I have to change the way I'm doing this and find a way around? CRTP is giving me hope the code might be valid, but I'm not sure.

If this really isn't possible, then why exactly, and why does CRTP work?

like image 254
coyotte508 Avatar asked Sep 05 '11 23:09

coyotte508


1 Answers

The solution was, as ildjarn pointed out, to add another level of indirection.

That is done by changing the test function to accept types:

template <typename X, class U = void>
struct test {
    typedef U type;
};

And then pass the child class as a template parameter, instead of specifying it from the get go:

    template<class Y=workerClass>
    typename test<decltype(&Y::fly)>::type 
    invoke2(Param<Fly>) {
        wc()->fly();
    }

    template<class Y=workerClass>
    typename test<decltype(&Y::swim)>::type 
    invoke2(Param<Swim>) {
        wc()->swim();
    }

That way the nested specifier is evaluated only when the function is called and not at the class evaluation, and by that time the child class is already evaluated. Plus with the possibility to pass default template argument, we can call the function without any template parameters.

The template is also much more readable now. And the sample code works just fine now:

class MyClass : public BaseClass<MyClass>
{
public:
    void swim() {
        printf("Swimming like a fish!\n");
    }

    /* void fly(); */
};


void testing() {
    MyClass foo;
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */
    foo.invoke<Swim>(); /* Should print the swimming message */
}
like image 155
coyotte508 Avatar answered Sep 27 '22 19:09

coyotte508