1

Consider this example in C#:

class C<T> {
   
   void greetMe() { print("Hello you"); }
}

class D : C<E> {
   void useE(E e) {
      e.greetMe();
   }
}

class E : C<D> {
   void useD(D d) {
      d.greetMe();
   }
}

Is an equivalent construction possible in C++ using templates?

I don't have any useful C++ code to show, since my interest in this is purely academic. You can consider this code as pseudocode.

For the curious: I was examining restrictions of eager languages to handle circular references, as opposed to lazy languages. I remembered that something like this is possible in C#'s type system, which I'm attempting to view as a statically checked lazy language. I remembered that C++'s templates are the exact opposite (but still somewhat functional?), which I thought might be an interesting study.
Thank you for your time!

Related question

markonius
  • 625
  • 6
  • 25

2 Answers2

3

You cannot have a complete type before it is defined, because the definition is what makes the type complete.

There may never be a cycle in the dependency graph.

That said, the class D does not require E to be complete. Only the definition of D::useE depends on that definition. So, following order of definition satisfies all dependencies: D, E, D::useE.

template <class T>
struct C {
   void greetMe();
};

struct E;
struct D : C<E> {
   void useE(E e);
};

struct E : C<D> {
   void useD(D d) {
      d.greetMe();
   }
};

void D::useE(E e) {
    e.greetMe();
};
eerorika
  • 232,697
  • 12
  • 197
  • 326
2

Extending from Not A Number's answer to the related question and embedding comments where I felt it was necessary:

template <class T>
class C {
public:
   void greetMe() {  }
};

class E; // forward declare E same as answer to related question
class D : public C<E> {
   void useE(E & e); // Passing by reference should be similar to the behaviour 
                     // of the C# program
                     // only declare function. Fully define later after D is complete
};

class E : public C<D> {
   void useD(D & d) { //D is already complete; we can safely define here
      d.greetMe();
   }
};

// now we can define D's implementation of useE 
void D::useE(E & e) {
   e.greetMe();
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
  • For me MSVC is actually happy to accept a declaration of `D::useE` that accepts `E` by value even before it knows the complete type of `E`, as long as the definition is placed after the definition of `E`. Is this MSVC being overly permissive, or is this actually allowed by the standard? – Nathan Pierson Jun 11 '21 at 19:29
  • Yes. I should remove that comment. I'll stand by leaving it a reference because it's closer to the behaviour of the C# original – user4581301 Jun 11 '21 at 19:31