Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A C++ covariance/overriding/circularity problem

I am writing a backend of a compiler of a subset of Java. The backend writes C++ code. There is some hypothetical Java code, though, that I do not known how to translate to C++.

An example problem is shown in the following code. A is extended by B, B is extended by C, and here are respective three header files A.h, B.h and C.h:


#ifndef A_H
#define A_H

class B;

class A {
  public: virtual B* get();
}

#endif /* !defined(A_H) */
==========================
#ifndef B_H
#define B_H

#include "A.h"

class C;

class B : public A {
  public: virtual C* get();
}

#endif /* !defined(B_H) */
==========================
#ifndef C_H
#define C_H

#include "B.h"

class C : public B {
}

#endif /* !defined(C_H) */

As can be seen, B overrides A's method get(). The overriding method returns a pointer to a respective subclass, which I guess is valid in C++, thanks to covariance.

What I do not know, is the way of informing the compiler, that C is indeed a subclass of B, and thus, that the overriding method is valid.

A forward declaration of C within B.h, just as the one seen in the code, is not enough, as it says nothing about C's superclasses.

An inclusion of C.h within B.h would be circular, as C.h already includes B.h. The latter is needed, because it is not enough to have only a forward declaration of the superclass.

What can be done with that?

EDIT Two remarks.

1 One of the poster claims, that the following is impossible in Java, so I add a Java version:

A.java:


class A {
  public B get() {
    return null;
  }
}

B.java:


class B extends A {
  public C get() {     
    return null;
  }
}

C.java:


class C extends B {
}

It compiles just fine.

2 I do not insist on compiling such somewhat strange cases. If they can not be translated to a readable code in C++, then fine, the backend will just fail with an error message. In fact, I am more interested in a general way of resolving the circular dependencies like that in C++.

EDIT 2

Thank you all, I am impressed by the efficiency of this site.

I concluded that, because:

  1. the header files generated are going to be used by other programmers;
  2. guessing from your answers, there is likely no solution that produces simple, readable header files;
  3. circular references involving return types are probably rare;
  4. I avoid C++ because, amongst others, it allows for solutions like that -- I know C++ has its uses, but personally, I prefer languages that have a simpler grammar, like Java;

the backend will:

  1. use forward declarations where possible;
  2. otherwise it will use includes, checking if the latter is circular; if yes, it will fail with an error message.

Cheers, Artur

like image 783
arataj Avatar asked Feb 21 '26 06:02

arataj


2 Answers

As you're generating code by machine, it's OK to use some dirty, filthy tricks.

class B;

class A {
  public: virtual B* CLASS_A_get();
}

class C;

class B : public A {
  public:
    virtual B* CLASS_A_get();
    virtual C* CLASS_B_get();
}

class C : public B {
}

// In B's .cpp file, you can include C.h
#include "C.h"
B* B::CLASS_A_get() {
    return CLASS_B_get();
}
like image 51
Puppy Avatar answered Feb 22 '26 19:02

Puppy


You can implement covariance yourself, cleanly, without relying on the compiler and without any casts. Here's a header with some circular dependencies:

class A
{
  public:
    A* get() { return get_A(); }
  private:
    virtual A* get_A();
};

class B : public A
{
  public:
    C* get() { return get_C(); }
  private:
    virtual A* get_A();
    virtual C* get_C();    
};

class C : public A
{
  public:
    B* get() { return get_B(); }
  private:
    virtual A* get_A();
    virtual B* get_B();    
};

You implement B::get_A in terms of B::get_C and C::get_A in terms of C::get_B. All get_* are implemented in the cxx file. You can because all three classes are already completely defined. The user always calls get().

Sorry if the formatting is wrong, I'm posting from a cell phone.

Edit: the solution with static casts is not always applicable, e.g. when virtual inheritance is involved (you will need dynamic casts then).

like image 39
n. 1.8e9-where's-my-share m. Avatar answered Feb 22 '26 21:02

n. 1.8e9-where's-my-share m.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!