Extend a template classe using the type definition in subclass

我们两清 提交于 2021-02-19 08:13:07

问题


I imitated the std::enable_shared_from_this to create a template class, but I made the class use the type definition in its subclass. Unfortunately! Although I used typename, after compiling,

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

GCC complained:

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> {
       ^~~~~~

How to solve it?

References

  • Where and why do I have to put the “template” and “typename” keywords?
  • C++ - meaning of a statement combining typedef and typename [duplicate]
  • Two template classes use each other as template argument

回答1:


Having a typedef at class level requires the template arguments to be complete types. How would the compiler otherwise be able to check, if the type provided as argument actually has some equivalent typedef itself?

Analogously, the following is going to fail:

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

Problem with curiously recurring template pattern, which is what you're trying to implement, that at the point you try to derive, the class is not yet complete, just as in my example above:

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

Ever wondered, though, why member variables are known within member functions even though being declared after the function? That's because

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

is compiled as if it was written as:

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

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

This is at the same time the clue where you can use the template argument the way you intend:

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

That won't help, though, with your members:

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

T still remains incomplete at this point.

Within the comments, though, you only seem to use the connection type. If you don't need the client or server class for any reason other than the typedef, you can solve the issue pretty simply:

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
{
    // ...
};

Otherwise, you need to fall back to other means, e. g. the pimpl pattern, where you would let the implementation class inherit from the template.

*) Identifiers starting with underscore followed by captial letter, as well as those containing two subsequent identifiers, are reserved for the implementation (i. e. for use by the compiler). Defining your own such ones yields undefined behaviour.


Edit (stolen from the comments):

If you need client or server from within Global, you could provide both as separate template paramters as well:

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>
{ /* ... */ };


来源:https://stackoverflow.com/questions/57546714/extend-a-template-classe-using-the-type-definition-in-subclass

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!