Inheriting constructors and virtual base classes
Asked Answered
A

2

6

I'm about to create an exception class hierarchy which conceptually looks somewhat like this:

#include <iostream>
#include <stdexcept>

class ExceptionBase : public std::runtime_error {
public: 
    ExceptionBase( const char * msg ) : std::runtime_error(msg) {}
};

class OperationFailure : virtual public ExceptionBase {
public: 
    using ExceptionBase::ExceptionBase;
};

class FileDoesNotExistError : virtual public ExceptionBase {
public: 
    using ExceptionBase::ExceptionBase;
};

class OperationFailedBecauseFileDoesNotExistError
    : public OperationFailure, FileDoesNotExistError {
public: 
    using ExceptionBase::ExceptionBase; // does not compile
};

int main() {
    OperationFailedBecauseFileDoesNotExistError e("Hello world!\n");

    std::cout << e.what();
}

All constructors should look the same as the constructor of the ExceptionBase class. The derived exceptions only differ concerning their type, there's no added functionality otherwise. The last exception type mentioned in the above code should also have these constructors. Is this possible using the inheriting constructors feature of the C++11 standard? If that is not possible: what are alternatives?

(By the way: In the above code the classes OperationFailure and FileDoesNotExistError did not compile with gcc 4.8, but with clang 3.4. Apparently, gcc rejects inheriting constructors for virtual bases. It would be interesting to know who's right here. Both compilers rejected the class OperationFailedBecauseFileDoesNotExistError, because the inheriting constructor does not inherit from a direct base.)

Alchemy answered 16/10, 2013 at 9:23 Comment(5)
Diamond inheritance? Sweet.Sucy
Fun begins in 3, 2, 1...Shackle
Maybe you need to use using OperationFailure::OperationFailure? But probably it wont work because of the double inheritance.Samaniego
Even a recent g++4.9 doesn't compile OperationFailure when creating an object (with the parametrized ctor). When replacing the virtual inheritance with a non-virtual, it work. I suggest you file a bug report for gcc, as I cannot find anything in the Standard which prohibits this, and the latest proposal N2540 explicitly allows it.Linalool
I will file a bug-report.Alchemy
L
2

When the using-declaration is used to inherit constructors, it requires a direct base class [namespace.udecl]/3

If such a using-declaration names a constructor, the nested-name-specifier shall name a direct base class of the class being defined; otherwise it introduces the set of declarations found by member name lookup.

I.e. in your case, the using-declaration in OperationFailedBecauseFileDoesNotExistError doesn't inherit, but re-declares (as an alias), or unhides, the name of the ctor of ExceptionBase.

You'll have to write a non-inheriting ctor for OperationFailedBecauseFileDoesNotExistError.


By the way, this is fine for non-virtual base classes: The using-declaration for inheriting ctors is rewritten as:

//using ExceptionBase::ExceptionBase;

OperationFailure(char const * msg)
: ExceptionBase( static_cast<const char*&&>(msg) )
{}

As you may only initialize a direct base class (or virtual base class) in the mem-initializer-list, it makes sense for non-virtual base classes to restrict the using-declaration to inherit ctors only from direct base classes.

The authors of the inheriting ctors proposal have been aware that this breaks support for virtual base class ctors, see N2540:

Typically, inheriting constructor definitions for classes with virtual bases will be ill-formed, unless the virtual base supports default initialization, or the virtual base is a direct base, and named as the base forwarded-to. Likewise, all data members and other direct bases must support default initialization, or any attempt to use a inheriting constructor will be ill-formed. Note: ill-formed when used, not declared.

Linalool answered 16/10, 2013 at 14:15 Comment(0)
S
1

Inhering constructors is like introducing wrapper functions for all the constructors you have specified. In your case, you must call the specific constructors of both OperationFailure and FileDoesNotExistError but the introduced wrappers will only call either of them.


I just checked the latest C++11 draft (section 12.9) but it doesn't really explicitly cover your case.

Samaniego answered 16/10, 2013 at 10:4 Comment(3)
The implicit declaration of default ctors is not inhibited by the using-declaration, therefore OperationFailure and FileDoesNotExistError still have default ctors. I'm not sure if they're well-formed however, as they don't initialize the virtual base class properly.Linalool
If this using-declaration in OperationFailedBecauseFileDoesNotExistError (let's call this class O) was legal, it would introduce a ctor equivalent to O(char const* msg) : ExceptionBase( static_cast<char const*&&>(msg) ) {}. However, this ctor is ill-formed, as it calls the deleted default ctor of both direct base classes. The latter are ill-formed (deleted) as they don't initialize the virtual base class, see [class.ctor]/5.Linalool
(Also see DR1567).Linalool

© 2022 - 2024 — McMap. All rights reserved.