c++inner-classesincomplete-type

Mutually referencing nested classes in C++


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:

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?


Solution

  • 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.