Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ exception inheritance ambiguity

Why does this work?

#include <exception>
#include <iostream>
#include <stdexcept>

#include <boost/exception/all.hpp>

struct foo_error : virtual boost::exception, public std::runtime_error
{
    explicit foo_error(const char* what)
        : std::runtime_error(what)
    { }

    explicit foo_error(const std::string& what)
        : std::runtime_error(what)
    { }
};

struct bar_error : virtual boost::exception, public std::runtime_error
{
    explicit bar_error(const char* what)
        : std::runtime_error(what)
    { }

    explicit bar_error(const std::string& what)
        : std::runtime_error(what)
    { }
};

struct abc_error : virtual foo_error, virtual bar_error
{
    explicit abc_error(const char* what)
        : foo_error(what), bar_error(what)
    { }

    explicit abc_error(const std::string& what)
        : foo_error(what), bar_error(what)
    { }
};

static void abc()
{
    throw abc_error("abc error");
}

int main()
{
    try
    {
        abc();
    }
    catch (const std::exception& e)
    {
        std::cerr << e.what();
    }
}

I thought this shouldn't compile due to the ambiguous conversion from abc_error to std::exception. What am I missing? I came up with the inheritance diagram, and I can't really figure out why this code works (the arrows denote virtual inheritance and the lines denote non-virtual inheritance).

  std::exception                  std::exception
        +                               +
        |                               |
        |                               |
        +                               +
std::runtime_error              std::runtime_error
        +                               +
        |                               |
        |   +-->boost::exception<-+     |
        +   |                     |     +
   foo_error+<-----+         +--->+bar_error
                   |         |
                   |         |
                   |         |
                   +abc_error+

It looks like abc_error includes two instances of std::exception so the catch (or so I thought) shouldn't be able to cast abc_error to std::exception. Or should it?

UPDATE

I can't answer my own questions at the moment, so I'm going to continue here. I've narrowed the problem down to:

struct NonVirtualBaseBase { };
struct NonVirtualBase : NonVirtualBaseBase { };

struct VirtualBase { };

struct A : virtual VirtualBase, NonVirtualBase { };
struct B : virtual VirtualBase, NonVirtualBase { };

struct C : A, B { };

int main()
{
    try
    {
        throw C();
    }
    catch (const VirtualBase& e)
    {
        return 1;
    }

    return 0;
}

The sample above works as expected and is a perfectly fine piece of code. It crashes if I replace catch (const VirtualBase& e) with catch (const NonVirtualBase& e) which I think is sane and makes sense. But it also works if I replace the same line with catch (const NonVirtualBaseBase& e) which to me seems odd and wrong. A compiler bug?

like image 852
Egor Tensin Avatar asked Oct 02 '22 07:10

Egor Tensin


1 Answers

UPDATE

As pointed out by the OP, this explanation doesn't quite cut it, since std::exception is not derived from using virtual inheritance. I believe the answer to be that this is actually undefined behaviour and simply not caught at compile time because there is no need for the throw and catch to know of each other and warn if they are incompatible.

END UPDATE

The answer is that this hierarchy uses*virtual inheritance* to derive from boost::exception.

Since both foo_error and bar_error uses virtual inheritance to inherit from boost::exception, there will only be a single boost::exception base which is shared between both the foo_error and bar_error sub-objects of an abc_error.

When you specify virtual before an entry in the list of base classes, this means all occurences of this class as a virtual base class in the most derived object will actually refer to the same instance. It is used specifically to avoid ambiguity in this type of design.

like image 158
Agentlien Avatar answered Oct 23 '22 18:10

Agentlien