Mutually referencing nested classes in C++

93 Views Asked by At

I have a number of situations where I need to mutually reference nested classes e.g.

class C1;
class C2;
// class C2::S; // not allowed by the standard    

template <class T> class R
{
  T* t = nullptr; // The only use of T as a data member is a T*
};

class C1
{
public:
  class S
  {
  public:
    R <C2::S> r; // Fails to compile because C2 is an incomplete type
  };
};

class C2
{
public:
  class S
  {
  public:
    R <C1::S> r;
  };
};

This fails because C2 is incomplete and at the point where it is used and so C2::S is not known to the compiler.

The question is in two parts:

  1. Is there a way to make this work in C++23 ?
  2. If not, is there, or has there been, any proposal to the C++ committee which would help?

There are a couple of 'obvious' solutions but I'm hoping not to have to use them:

  • Flatten the structure so there's no nesting. The actual examples I have are much more complex and this would make the code unreadable.
  • Use void* and then cast as necessary.

I was expecting to be able to use a dummy class in place of C2::S and then map it to C2::S before using it but it appears you can't declare a class and then redefine it with 'using', e.g. the following doesn't work:

class Z; // Z first defined as a class
class C1
{
public:
  class S
  {
  public:
    R<Z> r;
  };
};
class C2
{...}
using Z = C2::S; // Redefinition error (defining Z as a typedef vs. a class)

I've also tried with templates e.g.

template <class T> class TC1
{
public:
  class S
  {
  public:
  R <typename T::S> r;
  };
};

template <class T> class TC2
{
public:
  class S
  {
  public:

  R <typename T::S> r;

  };
};

using C1 = TC1 <TC2 <C1>>; // Error: C1 undeclared (or not in scope)

Regarding additions to the standard, I realize that allowing the use of incomplete types would never be acceptable as it would cause a lot of complexity e.g. in specifying what could and could not be used, or trying to define when different partially-defined versions of the same class are compatible or not.

I'm out of ideas. Any suggestions?

2

There are 2 best solutions below

3
Davis Herring On BEST ANSWER

This isn't the usual situation where people wish they could write

class A::B;

instead of defining A: you just want to have the nested classes refer to each other. That means that ordinary forward declarations work:

class C1
{
public:
  class S;
};

class C2
{
public:
  class S
  {
  public:
    R <C1::S> r;
  };
};

class C1::S
{
public:
  R <C2::S> r;
};

Obviously this depends on R not needing its template argument to be a complete type.

0
Ayman On

Can you use a forward declaration like so

template <class T>
class R
{
    T *t = nullptr; // The only use of T as a data member is a T*
};

class C1
{
public:
    class S;
};

class C2
{
public:
    class S
    {
    public:
        R<C1::S> r;
    };
};

class C1::S
{
public:
    R<C2::S> r;
};