19

Suppose I have these abstract classes Foo and Bar:

class Foo;
class Bar;

class Foo
{
public:
  virtual Bar* bar() = 0;
};

class Bar
{
public:
  virtual Foo* foo() = 0;
};

Suppose further that I have the derived class ConcreteFoo and ConcreteBar. I want to covariantly refine the return type of the foo() and bar() methods like this:

class ConcreteFoo : public Foo
{
public:
  ConcreteBar* bar();
};

class ConcreteBar : public Bar
{
public:
  ConcreteFoo* foo();
};

This won't compile since our beloved single pass compiler does not know that ConcreteBar will inherit from Bar, and so that ConcreteBar is a perfectly legal covariant return type. Plain forward declaring ConcreteBar does not work, either, since it does not tell the compiler anything about inheritance.

Is this a shortcoming of C++ I'll have to live with or is there actually a way around this dilemma?

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
Tobias
  • 6,388
  • 4
  • 39
  • 64
  • A lot us think of covariance as unecessary - see this question http://stackoverflow.com/questions/1260757/when-is-c-covariance-the-best-solution which as far as I'm concerned has failed to provoke a compelling answer. –  Aug 18 '09 at 12:24
  • 2
    I am working on a project with tons of kloc of existing code. Simply by covariantly changing the return type of a few methods I was able to get rid of many static_casts. If I had a compelling solution to the above problem, I could get rid of even more. – Tobias Aug 19 '09 at 14:46

4 Answers4

5

You can fake it quite easily, but you lose the static type checking. If you replace the dynamic_casts by static_casts, you have what the compiler is using internally, but you have no dynamic nor static type check:

class Foo;
class Bar;

class Foo
{
public:
  Bar* bar();
protected:
  virtual Bar* doBar();
};

class Bar;
{
public:
  Foo* foo();
public:
  virtual Foo* doFoo();
};

inline Bar* Foo::bar() { return doBar(); }
inline Foo* Bar::foo() { return doFoo(); }

class ConcreteFoo;
class ConcreteBar;
class ConcreteFoo : public Foo
{
public:
  ConcreteBar* bar();
protected:
  Bar* doBar();
};

class ConcreteBar : public Bar
{
public:
   ConcreteFoo* foo();
public:
   Foo* doFoo();
};

inline ConcreteBar* ConcreteFoo::bar() { return &dynamic_cast<ConcreteBar&>(*doBar()); }
inline ConcreteFoo* ConcreteBar::foo() { return &dynamic_cast<ConcreteFoo&>(*doFoo()); }
chrisaycock
  • 36,470
  • 14
  • 88
  • 125
AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • 2
    This solution works, but surely won't win a beauty contest :) – Tobias Aug 11 '09 at 10:41
  • If the participants are restricted to those solving the stated problem, I'm not so sure of the outcome :-) You obviously can use the language provided covariance for one of the class, but I preferred to keep the symmetry. – AProgrammer Aug 11 '09 at 11:01
  • 2
    @Tobias: I don't think that the original design with the complex dependencies would win a beauty contest either. :P – David Rodríguez - dribeas Aug 11 '09 at 12:58
4

Doesn't static polymorphism solve your problem? Feeding the base class with the derived class through template argument? So the base class will know the derivative Type and declare a proper virtual?

thAAAnos
  • 147
  • 1
  • 2
3

How about this.

template <class BarType>
class Foo
{
public:
    virtual BarType* bar() = 0;
};

template <class FooType>
class Bar
{
public:
    virtual FooType* foo() = 0;
};

class ConcreteBar;
class ConcreteFoo : public Foo<ConcreteBar>
{
public:
    ConcreteBar* bar();
};

class ConcreteBar : public Bar<ConcreteFoo>
{
public:
    ConcreteFoo* foo();
};
xYZ
  • 107
  • 1
  • 9
2

Covariance is based on inheritance diagram, so since you cannot declare

class ConcreteBar : public Bar;

hence no way to tell compiler about covariance.

But you can do it with help of templates, declare ConcretFoo::bar as template and later bounding allows you solve this problem

Dewfy
  • 23,277
  • 13
  • 73
  • 121
  • I know that you cannot forward declare inheritance. And templates won't help, either, since a template member function cannot be virtual. – Tobias Aug 11 '09 at 10:45
  • Not exactly: here my sample that works!
    
    class ConcreteFoo : public Foo
    {
    public:
        template 
        T* bar();
    };
    template <>
    ConcreteBar* ConcreteFoo::bar(){}
    
    
    – Dewfy Aug 11 '09 at 11:15
  • Dewfy: Please include a working sample in your answer, I cannot construct one from your given code. http://codepad.org/xl4NTdQt –  Jan 16 '10 at 18:21
  • @Roger Pate done, look at http://codepad.org/xl4NTdQt#comment-ayWjjk4p sample works on msvc, g++ – Dewfy Jan 18 '10 at 10:22
  • Ah, thanks, that's making ConcreteFoo a template rather than ConcreteFoo::bar as you first said. –  Jan 18 '10 at 16:30