Amirhessam
Amirhessam

Reputation: 154

Ambiguous Virtual Inhertiance

For an application, I need to create a set of special classes to handle exceptions. I derived my base class from std::exception. However, I ended up facing the diamond problem and ambiguous inheritance. Even using virtual inheritance didn't help. The following example demonstrates the problem.

#include <iostream>

class Exception:
    public virtual std::exception
{
public:
    Exception():
        std::exception("This is default")
    {        
        std::cout << "This is Exception\n";
    } 
};

class ChildException:
    public virtual std::runtime_error,
    public Exception

{
public:
    ChildException():
        Exception(),
        std::runtime_error("hello")
    {
        std::cout << "This is ChildException\n";
    }
};
int main() 
{
    ChildException exc();    
    //std::cout << static_cast<std::exception> (exc).what() << std::endl;
    //std::cout << static_cast<std::runtime_error> (exc).what() << std::endl;    
    getchar();
    return 0;
}

The code compiles but doesn't work. Any idea how to fix this?

Upvotes: 0

Views: 164

Answers (2)

Amirhessam
Amirhessam

Reputation: 154

Here is a different solution that addresses the diamond problem. In this solution, the base Exception class is not derived from std::exception. Instead, it contains a pointer to std::exception. Also, it has two converter functions that converts Exception to std::exception using its pointer. The derived class initializes the pointer using std::runtime_error. Using this approach, the ambiguity is resolved and a ChildException object can be caught as std::exception or std::runtime_error.

#include <iostream>

class Exception
{
public:    
    explicit Exception()
    {  
        pSTDException = nullptr;
    }
    operator std::exception*()
    {
        return pSTDException;
    }
    operator std::exception()
    {
        return * pSTDException;
    }
    std::exception * pSTDException;
};

class ChildException:
    public std::runtime_error,
    public Exception

{
public:
    ChildException():        
        std::runtime_error("This is ChildException")
    {        
        this->pSTDException = static_cast<std::exception *>(static_cast<std::runtime_error *>(this));        
    }    
};
int main() 
{    
    try
    {        
        throw ChildException();
    }    
    catch(std::exception & e)
    {
        std::cout << e.what();
    }
    getchar();
    return 0;
}

Upvotes: 0

I can see two issues at the moment:


1: The Most Vexing Parse

As pointed out by Brian in the comments, this line is actually a function prototype:

ChildException exc();

It can be read as either a ChildException named exc being initialised by calling the default constructor, or as a function named exc that returns a ChildException; I'm not sure of the exact reason, but the C++ standard dictates that in this situation, it will be read as the latter.

There are three ways to solve this:

  • Remove the parentheses: If you're just calling the default constructor, you can just write it without parentheses. This isn't always an option, however, as the most vexing parse can also catch you off-guard when you try to use direct-initialisation with a value obtained by a function call.

    ChildException exc;
    
    // Most vexing parse:
    ChildException ce;
    
    ChildException ce2(ce);
      // This is safe, it can't be read as a function prototype.
    ChildException ce3(ChildException());
      // This will be parsed as a function with:
        // Return type: ChildException
        // Parameter: Function pointer of type "ChildException (*)()".
    
  • Use copy initialisation: You can initialise it with assignment syntax, which the compiler will optimise away via copy elision.

    ChildException exc = ChildException();
    

    This works, but looks unnecessarily clunky, and runs the risk of being less efficient if you encounter a compiler that can't perform copy elision.

  • Use uniform initialisation: As of C++11, when using a compiler that supports uniform initialisation*, you can use braces instead of parentheses to specify a constructor call; considering the question's tags, I would recommend this approach.

    ChildException exc{};
    

    * [Out of the three "biggest" compilers, uniform initialisation is supported by Clang 3.1 or later, GCC 4.6 and later, and Visual Studio 2013 and later. While GCC supported it as of 4.4, and Visual Studio supported it as of 2012 CTP, earlier versions had difficulty with it in some circumstances; I'm unsure of whether early versions of Clang had issues with it.]


2: The Diamond Problem

I would assume that the code you're having trouble with is the two commented lines:

//std::cout << static_cast<std::exception> (exc).what() << std::endl;
//std::cout << static_cast<std::runtime_error> (exc).what() << std::endl;

Or more specifically, the problem is that the first of those lines results in an "ambiguous conversion" error, while the second line works properly. This is because ChildException actually has two std::exception base classes, each of which is separate from the other. The class layout looks something like this, specifically:

class ChildException    size(28):
    +---
    | +--- (base class Exception)
 0  | | {vbptr}
    | +---
    +---
    +--- (virtual base runtime_error)
    | +--- (base class exception)
 4  | | {vfptr}
 8  | | _Mywhat
12  | | _Mydofree
    | | <alignment member> (size=3)
    | +---
    +---
    +--- (virtual base exception)
16  | {vfptr}
20  | _Mywhat
24  | _Mydofree
    | <alignment member> (size=3)
    +---

Note, if you will, that while your Exception virtually inherits from std::exception, std::runtime_error does not. Due to this, its std::exception base is distinct from your Exception's std::exception base, and thus any attempt to cast ChildException to std::exception will be ambiguous, as it could refer to either the ChildException::Exception::exception base or the ChildException::runtime_error::exception base. If possible, I would suggest refactoring your exception classes so that each one inherits from a single std exception class at most. If this isn't possible, you can cast it through one of the base classes:

// Cast into std::exception through the base classes:
std::cout << "As Exception: "
          << static_cast<std::exception>(static_cast<Exception>(exc)).what()
          << std::endl;

std::cout << "As runtime_error: "
          << static_cast<std::exception>(static_cast<std::runtime_error>(exc)).what()
          << std::endl;

This isn't recommended, due to the issues caused by the diamond problem, but it is available if necessary. This is because each of these will access a different std::exception: The first will access the virtual one at the end of the class' layout, while the second will access the one inside the std::runtime_error.

Upvotes: 3

Related Questions