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

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

本文关键字:参数 嵌套 说明符 子类 基类      更新时间:2023-10-16

我使用我的类作为父类之一的模板形参,并且父类在模板参数中使用它(尽管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 %dn", 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工作?

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