1

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

samm
  • 620
  • 10
  • 22
  • 3
    Be aware that identifiers starting with underscore followed by a capital letter (`_S`, `_Subclass`!) as well as those containing two subsequent underscores are reserved for the implementation (i. e. the compiler). – Aconcagua Aug 18 '19 at 17:21
  • 3
    Unfortunately, C++ does not work this way. A class cannot be defined until its superclasses are defined first. Therefore, you can't reference something that's defined in a subclass from its superclass, using the CRTP design pattern. There is no solution. You must restructure things, somehow. – Sam Varshavchik Aug 18 '19 at 17:27
  • Could you explain a little more what you want to achieve, please? It's not clear to me from the code... From what i gather, it's CRTP you want... – jan.sende Aug 18 '19 at 17:39
  • @SamVarshavchik you can refrence something that's defined in a subclass from its superclass, that's the whole point of CRTP. But you only can do it inside superclass member functions. – n. m. could be an AI Aug 18 '19 at 17:47
  • 1
    How about `template class Global`? – n. m. could be an AI Aug 18 '19 at 17:48
  • Sam Varshavik is correct. The most straightforward workaround is to use type traits. – AndyG Aug 18 '19 at 17:52
  • No wonder that when compiling `template struct A {};` on my android mobile phone, GCC always complained: expected identifier before numeric constant . It seems that I must restructure and can do only in this way (@n.m) for my case. – samm Aug 19 '19 at 03:05

1 Answers1

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>
{ /* ... */ };
Aconcagua
  • 24,880
  • 4
  • 34
  • 59