2

I come from Java (OOP) background. I made a simple class to illustrate my problem:

#include <list>
#include <string>
#include <iostream>

// classes
class InterfaceA
{
public:
    virtual std::string functionA();
};

class InterfaceB
{
public:
    virtual std::string functionB();
};

class DerivedAB : public InterfaceA, public InterfaceB
{
public:
    std::string functionA()
    {
        return "I'm a A object";
    }

    std::string functionB()
    {
        return "I'm a B object";
    }
};

// functions
void doStuffOnListOfA(std::list<InterfaceA*> aElements)
{
    std::cout << "Print list of A" << std::endl;
    for (InterfaceA* const& a : aElements)
    {
        std::cout << a->functionA() << std::endl;
    }
};

int main()
{
    std::list<DerivedAB*> derivedABs;   
    doStuffOnListOfA(derivedABs);
    return 0;
}

I have two simple virtual classes InterfaceA and InterfaceB and a class DerivedAB that multi-inherits the two first virtual classes.

Furthermore, I then create a list of pointers of DerivedAB (std::list<DerivedAB *>) and wish to use this list with a function designed to work on a list of InterfaceA-derived objects. But I get an error:

(base)  ❮ onyr ★  kenzae❯ ❮ multi_inheritance_type_convertion❯❯ make
g++    -c -o main.o main.cpp
main.cpp: In function ‘int main()’:
main.cpp:54:32: error: could not convert ‘derivedABs’ from ‘std::__cxx11::list<DerivedAB*>’ to ‘std::__cxx11::list<InterfaceA*>’
     doStuffOnListOfA(derivedABs);                               

I have obviously a type casting error. I have read many articles on Stack Overflow about the different casting in C++ as well as on Multi-Inheritance, but my brain refuses to give me the answer.


Edits:

I said an erroneous statement:

"However I'm pretty sure such a code would work in Java..."

Apparently I'm missing an important concept about type inheritance...

JeJo
  • 30,635
  • 6
  • 49
  • 88
Onyr
  • 769
  • 5
  • 21
  • 3
    "*However I'm pretty sure such a code would work in Java...*" - no, it would not, becasue [collections of derived types are not derived from collections of base types](https://stackoverflow.com/q/2745265/7151494). This link explains the problem in Java which is pretty much the same in C++. I'd advise using `template`s. – Fureeish Aug 16 '21 at 16:12
  • 3
    F.A.Q.: [Is a parking-lot-of-Car a kind-of parking-lot-of-Vehicle?](https://isocpp.org/wiki/faq/proper-inheritance#parkinglot-of-car-vs-vehicle) – m88 Aug 16 '21 at 16:13
  • Hmm, sorry, I did'nt know that, I will read this thanks – Onyr Aug 16 '21 at 16:13

1 Answers1

3

C++ is far different from Java!


I have obviously a type casting error.

You are right about this (aka. type mismatch)! The std::list is a standard template container which gives you a concrete type, when you instantiate with a template argument. That means, the std::list<InterfaceA*> is different from std::list<DerivedAB *>.

This is exactly the compiler tells you:

error: could not convert 
from ‘std::__cxx11::list<DerivedAB*>’     ----> i.e. std::list<DerivedAB*>
to    ‘std::__cxx11::list<InterfaceA*>’   ----> i.e  std::list<InterfaceA*>
     doStuffOnListOfA(derivedABs);        ----> at the function call

You can not implicitly(i.e. compiler will not) convert to one another.

You need to cast each element of the derivedABs to base pointers or (in your case) make the doStuffOnListOfA as template function:

template<typename T>
void doStuffOnListOfA(std::list<T*> aElements)
{
    std::cout << "Print list of A" << std::endl;
    for (InterfaceA* a : aElements)
    {
        std::cout << a->functionA() << std::endl;
    }
};

To make sure that, one use the above only for std::list<derived from InterfaceA and B>, you may can (optionally) SFINAE the template function:

#include <type_traits> // std::is_base_of

template<typename T>
constexpr bool isBaseOfInterfaces = std::is_base_of_v<InterfaceA, T> && std::is_base_of_v<InterfaceB, T>;

template<typename T>
auto doStuffOnListOfA(std::list<T*> aElements) 
    -> std::enable_if_t<isBaseOfInterfaces<T>, void>
{
    // ... code
};

That being said,

  • You need to look into the smart pointers (such as std::unique_ptr, std::shared_ptr) rather than using raw pointers (manual memory management), by which you can handle the memory management smartly.
  • You might want to add the virtual destructor in your base classes for a defined behavior. See here for more: When to use virtual destructors?

Here is (the complete demo)

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • I'm sorry to bother you once more. I understand surprisingly quite well your high quality answer. Still, could you explain/give resources on the notation `auto stuff() -> std::stuff`. What is that `auto` since there is return ? – Onyr Aug 16 '21 at 20:55
  • 1
    @Onyr The syntax is called **[trailing return types](https://arne-mertz.de/2016/11/trailing-return-types-everywhere/)**, and the `std::stuff` is one of mayn ways of doing **[SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error)** and the `isBaseOfInterfaces` is a **[Variable template](https://en.cppreference.com/w/cpp/language/variable_template)**. Hope that helps! – JeJo Aug 17 '21 at 05:04