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.)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With