Использование дочернего класса в качестве параметра шаблона базового класса и в качестве спецификатора вложенного имени - PullRequest
6 голосов
/ 06 сентября 2011

Я использую мой класс в качестве параметра шаблона одного из его родительских классов, и этот родительский класс использует его в аргументе шаблона (хотя sizeof ()).

И компилятор выдаёт мне:

ошибка: неполный тип 'Invoker :: workerClass {aka MyClass}', используемый во спецификаторе вложенного имени

Покакласс хорошо определен в файле.Я предполагаю, что это потому, что дочерний класс не был создан в момент создания базового класса, но такого рода вещи случаются с CRTP и проблем нет.

Причина, по которой яиспользовать дочерний класс в аргументе шаблона - это сделать другой вызов функции, если дочерний класс имеет или не имеет определенной функции.

Вот минимальный код для тестирования

/* 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 */
}

Ошибка возникает в строке:

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

Итак, есть ли компилятор, который поддерживает это, или это явно указано стандартом как недопустимое использование шаблонов?Должен ли я изменить способ, которым я делаю это, и найти способ обойти?CRTP дает мне надежду, что код может быть действительным, но я не уверен.

Если это действительно невозможно, то почему именно, и почему CRTP работает?

1 Ответ

2 голосов
/ 06 сентября 2011

Решение было, как указал ildjarn, добавить еще один уровень косвенности.

Это делается путем изменения тестовой функции для приема типов:

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

И затем передать дочерний класс в качестве параметра шаблона, вместо того, чтобы указывать его с самого начала:

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

Таким образом, вложенный спецификатор оценивается только тогда, когда вызывается функция, а не при оценке класса, и к тому времени дочерний класс уже оценивается. Плюс с возможностью передачи аргумента шаблона по умолчанию, мы можем вызывать функцию без каких-либо параметров шаблона.

Шаблон также стал намного более читабельным. И пример кода теперь работает отлично:

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 */
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...