使用子类作为基类的模板参数和嵌套名称说明符

时间:2011-09-05 23:43:51

标签: c++ templates c++11

我正在使用我的类作为其父类之一的模板参数,并且该父类在模板参数中使用它(尽管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 个答案:

答案 0 :(得分:2)

正如ildjarn指出的那样,解决方案是添加另一个间接层。

通过将测试函数更改为接受类型来完成:

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

然后将子类作为模板参数传递,而不是从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();
    }

这样,嵌套说明符仅在调用函数时计算,而不是在类评估时计算,到那时,子类已经被计算。另外,可以通过默认模板参数,我们可以在没有任何模板参数的情况下调用该函数。

模板现在也更具可读性。示例代码现在运行正常:

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