Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheriting constructors and virtual base classes

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.)

like image 829
Ralph Tandetzky Avatar asked Oct 16 '13 09:10

Ralph Tandetzky


2 Answers

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.

like image 160
dyp Avatar answered Oct 08 '22 18:10

dyp


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.

like image 35
Albert Avatar answered Oct 08 '22 17:10

Albert