Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For an overloaded function, calling specialized version for parent and child instances

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.

like image 872
Luke Skywalker Avatar asked Apr 07 '15 11:04

Luke Skywalker


People also ask

What is calling overloaded function?

The function call operator, when overloaded, does not modify how functions are called. Rather, it modifies how the operator is to be interpreted when applied to objects of a given type.

What are the restrictions on overloaded function?

Restrictions on overloadingAny two functions in a set of overloaded functions must have different argument lists. Overloading functions that have argument lists of the same types, based on return type alone, is an error.

How are function calls matched with overloaded functions?

The process of matching function calls to a specific overloaded function is called overload resolution. Just because there is no exact match here doesn't mean a match can't be found -- after all, a char or long can be implicitly type converted to an int or a double .

What you call if a function name is overloaded with different task?

Using different parameters for the functions of same name to perform different types of tasks is known as function overloading.


3 Answers

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();
like image 75
Sergey Kalinichenko Avatar answered Oct 11 '22 00:10

Sergey Kalinichenko


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.

like image 44
Antonio Avatar answered Oct 10 '22 23:10

Antonio


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.

like image 20
luk32 Avatar answered Oct 11 '22 00:10

luk32