C++ inherit from multiple base classes with the same virtual function name
Asked Answered
J

5

26

I tried this code:

class A
{
    virtual void foo() = 0;
};

class B
{
    virtual void foo() = 0;
};

class C : public A, public B
{
    //virtual void A::foo(){}
    //virtual void B::foo(){}

    virtual void A::foo();
    virtual void B::foo();
};

void C::A::foo(){}
void C::B::foo(){}

int main()
{
    C c;
    return 0;
}

It is OK when using the commented part, but when I try to write the definitions outside the class declaration, the compiler reports errors. I am using the MSVC11 compiler, does anyone know how to write this? I need to move the code into the cpp file.

Thank you~~

Jaworski answered 23/8, 2013 at 8:36 Comment(5)
Commented part is also not working in gcc.Darlenadarlene
That makes no sense at all. It should just be virtual void foo();, and just once.Truckle
How do you want to use A, B and C? There are many possibilities: example 1, example 2, ... (those probably define more functions than necessary)Maltzman
[dcl.meaning]/1 Forbids the use of a qualified-id in the declaration of a (member) function: "When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers [...]"; therefore any virtual void X::foo(); is illegal (as a declaration inside C).Goldarn
Actually after DyP's remark the code linked in my previous comment is misleading, because C::A::foo really means A::foo (thanks @DyP ). Here are simpler examples: ideone.com/HCYM1X and ideone.com/vdRp3X .Maltzman
G
33

A function overrides a virtual function of a base class based on the name and parameter types (see below). Therefore, your class C has two virtual functions foo, one inherited from each A and B. But a function void C::foo() overrides both:

[class.virtual]/2

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.

As I already stated in the comments, [dcl.meaning]/1 forbids the use of a qualified-id in the declaration of a (member) function:

When the declarator-id is qualified, the declaration shall refer to a previously declared member of the class or namespace to which the qualifier refers [...]"

Therefore any virtual void X::foo(); is illegal as a declaration inside C.

The code

class C : public A, public B
{
    virtual void foo();
};

is the only way AFAIK to override foo, and it will override both A::foo and B::foo. There is no way to have two different overrides for A::foo and B::foo with different behaviour other than by introducing another layer of inheritance:

#include <iostream>

struct A
{
    virtual void foo() = 0;
};

struct B
{
    virtual void foo() = 0;
};

struct CA : A
{
    virtual void foo() { std::cout << "A" << std::endl; }
};

struct CB : B
{
    virtual void foo() { std::cout << "B" << std::endl; }
};

struct C : CA, CB {};

int main() {
    C c;
    //c.foo();  // ambiguous

    A& a = c;
    a.foo();

    B& b = c;
    b.foo();
}
Goldarn answered 23/8, 2013 at 10:48 Comment(4)
If CA::foo and CB::foo both need to access shared data within the object, you may need a third base class to contain the shared part - and then you get the dreaded diamond inheritance. What a mess!Oehsen
Note that abiguity can be explicitly resolved: c.A::foo();Donetta
What is the difference if I put virtual before all foo methods or just once? Does it matters if i put virtual to the method A::foo or B::foo or CA::foo , etc ? or it is irrelevant ?Pierrette
@CătălinaSîrbu A function is virtual if either you explicitly write virtual or if it has the same signature (name, parameters) as a virtual function in any base class. So most virtual keywords here are redundant. I prepend virtual to all virtual functions - even if it is not necessary - to show directly that the function is virtual.Goldarn
C
4

You've got just one virtual function foo:

class A {
    virtual void foo() = 0;
};

class B {
    virtual void foo() = 0;
};

class C : public A, public B {
    virtual void foo();

};

void C::foo(){}
void C::A::foo(){}
void C::B::foo(){};

int main() {
    C c;
    return 0;
}
Clupeoid answered 23/8, 2013 at 9:12 Comment(4)
Just a remark: The definitions void C::A::foo(){} and void C::B::foo(){} provide definitions for the pure virtual functions A::foo and B::foo, respectively (and are not required).Goldarn
as say dyp : void C::foo() {cout<<"C"<<endl;} void C::A::foo() {cout<<"A"<<endl;} void C::B::foo() {cout<<"B"<<endl;}; int main() { C c; c.foo(); ( (A&)c ).foo(); ( (B&)c ).foo(); return 0; } always output 'C'Germanic
@Goldarn Just curious why it's legal to use syntax void C::A::foo(){} to provide definition for A::foo(), any benefits to use this kind of syntax rather than overriding A::for(){ } directly? It looks very confusing to me, what if A is an inner class? I compiled this code and was surprised it's legal.Ingroup
@Ingroup It's a consequence of general concepts. When you define a member function, you simply use one of the ways to refer to the name of the member; the same ways of referring can be used when you want to call the function, e.g. this->C::A::foo(); The name A is a member of C because class A implicitly defines a member named A referring to itself (injected-class-name). This member A::A is visible inside of C because of the inheritance.Goldarn
L
3

I stepped into the same problem and accidentially opened a second thread. Sorry for that. One way that worked for me was to solve it without multiple inheritance.

#include <stdio.h>

class A
{
public:
    virtual void foo(void) = 0;
};

class B
{
public:
    virtual void foo(void) = 0;
};


class C
{
    class IA: public A
    {
        virtual void foo(void)
        {
            printf("IA::foo()\r\n");
        }
    };
    class IB: public B
    {
        virtual void foo(void)
        {
            printf("IB::foo()\r\n");
        }
    };

    IA m_A;
    IB m_B;
public:
    A* GetA(void)
    {
        return(&m_A);
    }

    B* GetB(void)
    {
        return(&m_B);
    }
};

The trick is to define classes derived from the interfaces (A and B) as local classes (IA and IB) instead of using multiple inheritance. Furthermore this approach also opens the option to have multiple realizations of each interface if desired which would not be possible using multiple inheritance. The local classes IA and IB can be easily given access to class C, so the implementations of both interfaces IA and IB can share data.

Access of each interface can be done as follows:

main()
{
    C test;
    test.GetA()->foo();
    test.GetB()->foo();
}

... and there is no ambiguity regarding the foo method any more.

Loftus answered 18/6, 2018 at 14:18 Comment(0)
S
1

You can resolve this ambiguity with different function parameters.

In real-world code, such virtual functions do something, so they usually already have either:

  1. different parameters in A and B, or
  2. different return values in A and B that you can turn into [out] parameters for the sake of solving this inheritance problem; otherwise
  3. you need to add some tag parameters, which the optimizer will throw away.

(In my own code I usually find myself in case (1), sometimes in (2), never so far in (3).)

Your example is case (3) and would look like this:

class A
{
public:
    struct tag_a { };
    virtual void foo(tag_a) = 0;
};

class B
{
public:
    struct tag_b { };
    virtual void foo(tag_b) = 0;
};

class C : public A, public B
{
    void foo(tag_a) override;
    void foo(tag_b) override;
};
Sociopath answered 29/9, 2020 at 22:15 Comment(0)
G
0

A slight improvement over adigostin's solution:


#include <iostream>

struct A {
    virtual void foo() = 0;
};

struct B {
    virtual void foo() = 0;
};

template <class T> struct Tagger : T {
    struct tag {};
    void foo() final { foo({}); }
    virtual void foo(tag) = 0;
};

using A2 = Tagger<A>;
using B2 = Tagger<B>;

struct C : public A2, public B2 {
    void foo(A2::tag) override { std::cout << "A" << std::endl; }
    void foo(B2::tag) override { std::cout << "B" << std::endl; }
};

int main() {
    C c;
    A* pa = &c;
    B* pb = &c;
    pa->foo(); // A
    pb->foo(); // B
    return 0;
}

Assuming that the base classes A and B are given and cannot be modified.

Gulgee answered 6/1, 2022 at 6:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.