首页 > 解决方案 > 使用子类中的类型定义扩展模板类

问题描述

我模仿std::enable_shared_from_this创建了一个模板类,但是我让这个类在它的子类中使用了类型定义。很遗憾!虽然我用过typename,编译后,

// 
// https://ideone.com/eYCBHW  http://ideone.com/eYCBHW
#include <iostream>
#include <set>
#include <map>
using namespace std;

template<class _S> struct A {
};

template<class _Subclass>
class Global {
public:
    typedef typename _Subclass::connection_t connection_t;
    //std::map<std::string, _Subclass::connection_t> connections;
    //std::set<_Subclass::connection_t> connections;
    //using typename _Subclass::connection_t;
    //typename _Subclass::connection_t* connections;
    //connection_t* connections;
};

class CConnection {};

class SConnection;

class Client : public Global<Client> {
public:
    typedef CConnection connection_t;
};

#if 0
class Server : public Global<Server> {
public:
    typedef SConnection connection_t;
};
#endif

class SConnection {};

int main() {
    // your code goes here
    return 0;
}

海合会抱怨:

prog.cpp: In instantiation of ‘class Global<Client>’:
prog.cpp:25:23:   required from here
prog.cpp:14:43: error: invalid use of incomplete type ‘class Client’
  typedef typename _Subclass::connection_t connection_t;
                                           ^~~~~~~~~~~~
prog.cpp:25:7: note: forward declaration of ‘class Client’
 class Client : public Global<Client> {
       ^~~~~~

如何解决?

参考

标签: c++templates

解决方案


具有typedef类级别要求模板参数是完整类型。如果作为参数提供的类型实际上具有一些等效的 typedef 本身,编译器将如何检查?

类似地,以下将失败:

class C;
using G = Global<C>; // C is not a complete type!
class C // too late...
{
    // ...
};

奇怪地重复出现的模板模式的问题,这是您尝试实现的,在您尝试派生时,该类尚未完成,就像我上面的示例一样:

class Client : public Global<Client> // client is not yet complete!
{
}; // only HERE, it will get complete, but that's too late for above

但是,有没有想过,为什么成员变量在成员函数中是已知的,即使是在函数之后声明的?那是因为

class C
{
    void f() { n = 12; }
    int n = 10;
};

编译为好像它被写成:

class C
{
    inline void f();
    int n = 10;
};

void C::f() { n = 12; } // n is known now!

这同时也是可以按照您的意图使用模板参数的线索:

template<class T> // different name used! *)
class Global
{
public:
    void f()
    {
        typedef typename T::connection_t connection_t; // possible here!
        // (similar to why you can use the static cast as in the link provided)
    }
};

但是,这对您的成员没有帮助:

std::map<std::string, typename T::connection_t> connections;
//                     ^ additionally was missing, but won't help either

T在这一点上仍然不完整。

但是,在评论中,您似乎只使用了连接类型。如果您因为 typedef 以外的任何原因不需要客户端或服务器类,则可以非常简单地解决问题:

template<class T> // different name used! *)
class Global
{
    std::map<std::string, T> connections;
    //                    ^  use T directly
};

class Client : public Global<CConnection>
//                             ^ make sure it is defined BEFORE
{
    // ...
};

否则,您需要回退到其他方式,例如pimpl 模式,您可以让实现类从模板继承。

*) 标识符以下划线开头,后跟大写字母,以及包含两个后续标识符的标识符,保留用于实现(即供编译器使用)。定义自己的此类会产生未定义的行为。


编辑(从评论中窃取):

如果您需要来自内部的客户端或服务器Global,您也可以将两者作为单独的模板参数提供:

template <typename Base, typename Connection>
{
    // use Connection directly, e. g. for member definitions
    // and Base within member functions as mandated by CRTP
};

class Client : public Global<Client, CConnection>
{ /* ... */ };

推荐阅读