Ambiguous conversion in dynamic_cast
Asked Answered
B

4

5

Consider the problem of getting an object as argument and printing its type:

#include <iostream>

class A { };
class B : public A { };
class C : public A { };
class D : public C, public B { };

using namespace std;

template<class T>
void print_type(T* info)
{
    if(dynamic_cast<D*>(info))
        cout << "D" << endl;
    else if(dynamic_cast<C*> (info))
        cout << "C" << endl;
    else if(dynamic_cast<B*>(info))
        cout << "B" << endl;
    else if(dynamic_cast<A*> (info))
        cout << "A" << endl;
}

int main(int argc, char** argv)
{
    D d;
    print_type(&d);
    return 0;
}

It gives me the following error: "Ambiguous conversion from derived class 'D' to base class."
But I fail to see where's the ambiguity: if the object declared in main (d) is of type D, why can't be it directly converted to a type A?

Also, if I pass an argument of type string of course I get other errors:
'std::basic_string<char>' is not polymorphic

In Java for generics there is the syntax: <T extends A>; in this case it would be useful. How can I make a similar thing in C++ with templates?


I have modified the code this way:

#include <iostream>
#include <vector>

class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public C, public B { };

using namespace std;

template<class T>
void print_type(T* info)
{
    if(dynamic_cast<D*>(info))
        cout << "D" << endl;
    else if(dynamic_cast<C*> (info))
        cout << "C" << endl;
    else if(dynamic_cast<B*>(info))
        cout << "B" << endl;
    else if(dynamic_cast<A*> (info))
        cout << "A" << endl;
}

int main(int argc, char** argv)
{
    string str;
    print_type(&str);
    return 0;
}

But I still get the error: 'std::basic_string<char>' is not polymorphic

Bhayani answered 5/4, 2012 at 23:12 Comment(4)
I assume this is just an illustrative example, not real code? Because of course, this is exactly the sort of thing that's solved with polymorphism.Orr
It's not illustrative, but I tried this code just to see how was dynamic_cast working.Bhayani
@RamyAlZuhouri See MSDN about that. "You cannot use dynamic_cast to convert from a non-polymorphic class (a class with no virtual functions)."Havens
It is ambiguous because the cast cannot determine how to cast. Keep in mind that the content of a class is inherited, not some magical superpower . You inherit from two different empty classes that inherit from an empty base class. How to you expect the compiler to figure out what you want to do. This is programming not magic...Eyde
M
0

Consider the problem of getting an object as argument and printing it's type:

Sigh... use RTTI.

#include <iostream>
#include <string>
#include <typeinfo>

template<class T> void print_type(const T& info){
    std::cout << typeid(info).name() << std::endl;
}

int main(int argc, char** argv){
    D d;
    int a = 3;
    std::string test("test");
    print_type(d);
    print_type(a);
    print_type(test);
    return 0;
}
Mull answered 5/4, 2012 at 23:41 Comment(0)
U
8

First of all, this is not a templates problem. If you remove the template and just have print_type take a D*, you'll see that the error will still be there.

What is happening is you do not use virtual inheritance, hence you get this situation:

A   A
|   | 
B   C
 \ /
  D

The dynamic_cast doesn't know which A you are refering to.

To achieve this: (and I assume it's what you wanted)

  A
 / \
B   C
 \ /
  D

...you should use virtual inheritance, ergo:

class A
{
};

class B : virtual public A
{
};

class C : virtual public A
{
};

class D : public C,public B
{
};

... and now it compiles without problems :) (keep in mind that Virtual Inheritance Is Evil though)

Unshaped answered 5/4, 2012 at 23:21 Comment(1)
It was exactly my doubt! Thank youGranjon
H
3

This is called a deadly diamond of death, or simply, diamond problem. The "path" to A can go through either B or C, hence a potential contradiction.

Furthermore, the idea of a template is to make it generic, not type aware. A template is not in itself compiled code, it's compiled against its use. It's a lot like a big macro.

Havens answered 5/4, 2012 at 23:19 Comment(4)
Virtual inheritance solves this. Google it if you are curious. Here's the Wikipedia article about it: en.wikipedia.org/wiki/Virtual_inheritanceTransposition
@trinithis: to say virtual inheritance "solves this" is misleading - the design as shown has two A objects and may well need them... changing to have a shared A object may or may not be viable.Nansen
It's not a diamond, because there are just two A classes created.Unshaped
My understanding was that the diamond was a conceptual error, one of design, not of creation, since it doesn't compile. I need to research this again, I never run into cases like this IRL.Havens
M
0

Consider the problem of getting an object as argument and printing it's type:

Sigh... use RTTI.

#include <iostream>
#include <string>
#include <typeinfo>

template<class T> void print_type(const T& info){
    std::cout << typeid(info).name() << std::endl;
}

int main(int argc, char** argv){
    D d;
    int a = 3;
    std::string test("test");
    print_type(d);
    print_type(a);
    print_type(test);
    return 0;
}
Mull answered 5/4, 2012 at 23:41 Comment(0)
S
0

In order to make class polymorphic you should define virtual destructor

In you case error occurs because you're trying to apply dyamic_cast to std::string, which doesn't have virtual destructor.

Yet-to-be discovered error is that class A in your case is also not polymorphic, thus whole hierarchy is not suitable for dynamic_cast.

Sparteine answered 11/5, 2023 at 17:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.