6

I asked a question earlier but it turns out my problem was not properly modeled by my example. So here is my actual problem:

  1. I have class A, and class B inheriting from A,
  2. I have two functions foo(A&) and foo(B&),
  3. I have a list of A* pointers, containing instances of A and B.
  4. How do I get to call foo(A&) for instances of A and foo(B&) for instances of B? Constraints: I can modify A and B implementation, but not foo's implementation.

See below an example:

#include <iostream>
#include <list>

class A {
public:
};

class B : public A {
public:
};

void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it);
}

Although I am using a container with pointers, bar is called with object from the parent class, not the child class:

# ./a.out
This is an A
This is an A
#

I was expecting

This is a B

Passing pointers to bar (by rewriting its signature) does not help.

Thx to Antonio for helping clarifying the question.

Community
  • 1
  • 1
Luke Skywalker
  • 1,464
  • 3
  • 17
  • 35
  • 4
    The type if `**it` is `A`, so why were you expecting "This is a B"? – juanchopanza Apr 07 '15 at 11:30
  • 2
    Make instead virtual method `A::bar()` and `B::bar()` and you will see what you expect. – Jarod42 Apr 07 '15 at 11:32
  • Because I am allocating a B in it. – Luke Skywalker Apr 07 '15 at 11:32
  • 1
    It doesn't matter. How can overload resolution know that a pointer to an `A` actually points to a `B`? Overload resolution happens at compile time. – juanchopanza Apr 07 '15 at 11:33
  • @Jarod42 That was my answer, but the functions are not members =). OP would need to cast `A` to `B`. – luk32 Apr 07 '15 at 11:33
  • Bar is not a member of my class in my problem. – Luke Skywalker Apr 07 '15 at 11:34
  • 2
    @LukeSkywalker: If you want overloading to work properly on base class pointers, you have to use virtual member functions. – AndyG Apr 07 '15 at 11:35
  • AndyG: yes but that's not an option for me here. In my real world problem, bar is a method of another class. Maybe I need to use a virtual proxy method to call bar... – Luke Skywalker Apr 07 '15 at 11:36
  • @LukeSkywalker: So you are required to have polymorphism and not allowed to create a virtual member function? – AndyG Apr 07 '15 at 11:43
  • AndyG: Yes I can create virtual members, but in fine, the code I need to execute is not in my class. But by using your proposition, I still can create a virtual member method foo which will call bar. This could be a solution to my problem, although it's a little bit convoluted... – Luke Skywalker Apr 07 '15 at 11:46
  • `A` is not a polymorphic class so there is actually no way you can access the `B` part of anything via the list and also you can't delete the pointers in the list. (Well, you can `static_cast` however that causes UB if you do it on something that isn't a `B` and you have no way of knowing in general code). – M.M Apr 07 '15 at 11:49
  • @LukeSkywalker the function doesn't have to be in your class but your derived class must override something from your base class. Then you can use dynamic_cast to downcast A to B. I have added the answer. – King Apr 07 '15 at 11:58
  • @LukeSkywalker I believe this could be a very good question (=Valuable for future users) if you just make a little bit more clear your question on what you exactly want, and why you want it in that way. Said in other words, as the question is posed now, many answers here below are correct, but you were looking exactly for dasblinkenlight answer. – Antonio Apr 07 '15 at 12:18
  • @Antonio I have precised my question. Let me know if it isn't clear. – Luke Skywalker Apr 07 '15 at 14:25
  • @LukeSkywalker I do not understand how you can use dasblinkenlight answer and keep bar() as a method of another class: how would you call that method from within A and B, as required by his solution? – Antonio Apr 07 '15 at 15:06
  • @antonio: because I have a handle of the class holding bar within A. A (and B) are actually Observable and my bar function is the notify method of the Observer. – Luke Skywalker Apr 07 '15 at 15:14
  • @LukeSkywalker I do appreciate your effort in reducing your actual problem to a minimum code sample, but I believe adding this Oberver class into the picture would have helped to understand better your problem. – Antonio Apr 07 '15 at 15:26
  • @antonio: Sorry if the problem wasn't clear initially. The thing is the problem I mention is a general problem and to introduce more functional details may have guided you to offer an alternate design which would not have helped me. Knowing this now, what would be your proposal? – Luke Skywalker Apr 07 '15 at 15:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/74650/discussion-between-antonio-and-luke-skywalker). – Antonio Apr 07 '15 at 15:48

5 Answers5

6

Since overloading is resolved at compile time, you need to supply the compiler with enough information to decide on the proper overload of bar to call. Since you wish to make that decision dynamically based on the run-time type of the object, virtual functions would be of great help:

struct A {
    virtual void bar() { bar(*this); }
};

struct B : public A {
    virtual void bar() { bar(*this); }
};

It may seem like the bodies are identical, so B::bar could be eliminated, but this is not true: although the bodies look exactly the same, they call different bars due to the static resolution of overloads in C++:

  • Inside A::bar the type of *this is A&, so the first overload is called.
  • Inside B::bar the type of *this is B&, so the second overload is called.

Modify the calling code to call the member bar will complete the change:

std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • dasblinkenlight: Spot on. – Luke Skywalker Apr 07 '15 at 12:06
  • @dasblinkenlight Yep, that's what OP was looking for, I had not fully got it. Out of curiosity: do you know if this is considered a standard/recommendable way to operate? – Antonio Apr 07 '15 at 12:09
  • @Antonio Under the constrains that OP has described (i.e. no control over the existing overloads of `bar`) this is reasonably common. For example, if one wanted to provide an OOP layer on top a C library, he would take a similar approach, but instead of passing `*this` he would pass some "handle" obtained from the library and stored inside a subclass. – Sergey Kalinichenko Apr 07 '15 at 12:14
  • I essentially had same idea after reading comments. For what it's worth if this is will be the top answer, what will be picked up and called inside `B` is not so obvious. You need to properly declare everything. And working example needs some more work. In particular `::bar(A&)` needs to know `A`, then `A::bar()` needs to know there is `::bar(A&)` so you need forward declaration for something to avoid circular dependencies. And you need to remember, you need to declare it for every descendant of `A` that needs to override it, otherwise, compiler will implicitly convert `B&` to `A&`... – luk32 Apr 07 '15 at 12:23
4

Edit: This answer the first version of the question, now see instead dasblinkenlight's solution.


If you do:

A* b = B();

Then *b will be of type A. That's what you are doing in your for cycle. There's no "virtuality" or polimorfism involved in this.

The following code gives the behaviour you are looking for:

class A {
public:
virtual void bar() { std::cout << "This is an A" << std::endl; }
};

class B : public A {
public:
virtual void bar() { std::cout << "This is a B" << std::endl; }
};



int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  l.push_back(new A());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

Taking my example above, in that case:

b->bar();

will print This is a b.

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • OP is asking for a solution for non member function, and it seems it's not an XY question. – luk32 Apr 07 '15 at 11:44
  • @luk32 I believe my answer addresses the question here. Let's see how OP reacts. – Antonio Apr 07 '15 at 11:47
  • Antonio, the answer I would accept would be if bar remains a function outside the classes and is called by a virtual member method ot A and B (called foo for example) which would itself call bar. – Luke Skywalker Apr 07 '15 at 11:49
  • @Antonio Sure, if we are allowed to change class signature. It's the proper way. – luk32 Apr 07 '15 at 11:50
  • Yes I have control over the class, not over the function bar. – Luke Skywalker Apr 07 '15 at 11:50
  • @LukeSkywalker If you have control over the way the class is written, then make a bar function that uses the pointer to call the bar method. Namely, take my code and change your `foo` function definition to `void bar(A* a) {a->bar();}` – Antonio Apr 07 '15 at 11:57
  • Yes, that's what I mention 2 comments above :) – Luke Skywalker Apr 07 '15 at 11:58
  • @LukeSkywalker I believe what you said it's different. In the method I propose, the virtual method does not call the external function, the opposite is true. – Antonio Apr 07 '15 at 12:00
  • 1
    Note that it is possible to delegate to a free function ; e.g. the free `bar()` may remain as OP had it, and the virtual function would be `virtual void bar_helper() { bar(*this); }`, You'd need to write that in every class (you could derive via CRTP to make it look a bit tidier) – M.M Apr 07 '15 at 12:02
  • Antonio: I have edited your answer with what I believe is closest to what could be achieved with a simple and clean enough design. – Luke Skywalker Apr 07 '15 at 12:04
  • @LukeSkywalker I suggest you append that to your question. I saw the edit, I think it deviates with the intention of my answer. – Antonio Apr 07 '15 at 12:07
2

You are looking for run-time polymorphism. This is supported "naturally" for virtual member methods.

An alternative would be to use RTTI and dynamically cast A* to B* and call bar upon success... or static_cast if you are really sure there are B* objects. Generally need to down-cast indicates problematic design.

Important note: Run-time check in dynamic_cast requires the type to be polymorphic anyways. Maybe your particular A fulfills this but you just can't change the class. If not, static_cast is the only option available.

If you have control over class you, can use standard polymorphism and overload mechanisms using virtual methods on this as a facade for the "external" call:

#include <iostream>
#include <list>

class A;
void external_bar(A&);

class A {
public:
virtual void bar() { external_bar(*this); };
};

class B;
void external_bar(B&); //IMPORTANT
class B : public A {
public:
virtual void bar() { external_bar(*this); };
};

void external_bar(A &a) { std::cout << "This is an A" << std::endl; }
void external_bar(B &b) { std::cout << "This is a B" << std::endl; }


int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    (*it)->bar();
}

This also has drawbacks. Forward declarations are needed. And you need to take care everything is defined properly, because if you forget line // IMPORTANT the compiler will pick up the definition of external_bar for A& as it is implicitly convertible, and you might get quite a headache spotting the error.

luk32
  • 15,812
  • 38
  • 62
  • Honestly sounds like this is the route that OP will go down. There's always the uglier solution to use a typesafe union like `boost::variant` – AndyG Apr 07 '15 at 11:44
  • Another option is indirectly provided by AndyG. Use a virtual member method that would call bar. Tried it and it works, but I can't post code in a comment. – Luke Skywalker Apr 07 '15 at 11:48
1

Others have already explained how it can be achieved.

I'll just limit myself to why it is so.

B gets implicitly cast to A here. So it currently has only properties of A.

The upward casting is implicit in C++.

Downcasting in C++ is possible only if your base class is polymorphic.

In short polymorphic requirement is nothing but something in your base class that can be overridden by your derived!! Virtual methods

then you can use RTTI and dynamic_cast as prescribed by others to do that.

Example:

#include <iostream>
#include <list>

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

class B : public A {
public:
void dummy() { }
};

void bar(A &a) { std::cout << "This is an A" <<  std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

int main(int argc, char **argv) {
  std::list<A *> l;
  l.push_back(new B());
  l.push_back(new B());

//Prints A
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(**it); 

//Prints B
  for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
    bar(dynamic_cast<B&>(**it));
}


Answer:
This is an A
This is an A
This is a B
This is a B

Note: This is only if your list has objects of type B. Otherwise, its going to crash out. This only explains upcast vs downcast

King
  • 1,170
  • 2
  • 16
  • 33
  • The `Prints B` loop will throw an exception for any non-B items in the list; I'm guessing that this code is meant to be extensible to the list containing various types of item – M.M Apr 07 '15 at 11:59
  • True. If it will be list of objects of anytype from A, then he has to have virtual function in base which will be overridden in subsequent children. similar to the answer. – King Apr 07 '15 at 12:14
  • @MattMcNabb I just tried to explain downcast and implicit upcast with this. – King Apr 07 '15 at 12:15
  • To be completely correct `static_cast` also allows down-casting, but it's running with untied shoe laces. – luk32 Apr 07 '15 at 12:24
0

Antonio wrote a good solution involving virtual functions. If you really do not want to use a virtual function for some reason, then you can use dynamic_cast in a free function instead:

#include <iostream>
#include <list>

struct A {
    virtual ~A() {}   // important
};

struct B : A {};

void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }

void bar_helper(A *ptr)
{
    if ( auto b = dynamic_cast<B *>(ptr) )
        bar(*b);
    else
        bar(*ptr);
}

int main()
{
    std::list<A *> ls;
    ls.push_back(new B);
    ls.push_back(new B);
    ls.push_back(new A);

    for (auto ptr : ls)
    {
        bar_helper(ptr);
        delete ptr;
    }
    ls.clear();
}
M.M
  • 138,810
  • 21
  • 208
  • 365