jotik
jotik

Reputation: 17920

std::throw_with_nested expects default constructor for virtual base class of Exception?

Why does this not compile (tried with Clang 3.4.2 and GCC versions 4.7.4, 4.8.3 and 4.9.1):

#include <exception>

struct Base {
  inline Base(int) {}
  virtual void f() {}
};

struct Derived: virtual Base {
  inline Derived() : Base(42) {}
};

int main() {
  std::throw_with_nested(Derived());
  return 0;
}

Errors from GCC 4.9.1:

In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/exception:163:0,
                from test.cpp:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h: In instantiation of 'std::_Nested_exception<_Except>::_Nested_exception(_Except&&) [with _Except = Derived]':
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:126:60:   required from 'void std::__throw_with_nested(_Ex&&, ...) [with _Ex = Derived]'
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:140:58:   required from 'void std::throw_with_nested(_Ex) [with _Ex = Derived]'
test.cpp:13:35:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:81:45: error: no matching function for call to 'Base::Base()'
      : _Except(static_cast<_Except&&>(__ex))
                                            ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:81:45: note: candidates are:
test.cpp:4:10: note: Base::Base(int)
  inline Base(int) {}
          ^
test.cpp:4:10: note:   candidate expects 1 argument, 0 provided
test.cpp:3:8: note: constexpr Base::Base(const Base&)
struct Base {
        ^
test.cpp:3:8: note:   candidate expects 1 argument, 0 provided
test.cpp:3:8: note: constexpr Base::Base(Base&&)
test.cpp:3:8: note:   candidate expects 1 argument, 0 provided

If I omit the virtual keyword I get no errors. Is this a GCC and Clang or libstd++ bug, or am I doing something wrong?

PS: Note that virtual void f() {} is a workaround for this bug.

Upvotes: 0

Views: 152

Answers (2)

The standard states:

For each distinct base class that is specified virtual, the most derived object shall contain a single base class subobject of that type.

§10.1 [class.mi]

Also, with respect to std::throw_with_nested:

[[noreturn]] template <class T> void throw_with_nested(T&& t);

Let U be remove_reference<T>::type.

Requires: U shall be CopyConstructible.

Throws: if U is a non-union class type not derived from nested_exception, an exception of unspecified type that is publicly derived from both U and nested_exception and constructed from std::forward<T>(t), otherwise std::forward<T>(t).

§18.8.6 [except.nested]

If you look in bits/nested_exception.h, you'll find the following, which is the base class that the library creates for std::throw_with_nested in libstdc++:

template<typename _Except>
struct _Nested_exception : public _Except, public nested_exception
{
  explicit _Nested_exception(_Except&& __ex)
  : _Except(static_cast<_Except&&>(__ex))
  { }
};

This derives from your Derived class, and attempts to initialize itself with your copy or move constructor, both of which are implicitly defined.

In the case of non-virtual inheritance, this would be fine, since base classes are recursively copied or moved by implicit copy/move constructors, as laid out in the standard:

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.

§12.8 [class.copy]

However, because of the virtual inheritance, and §10.1 [class.mi] (quoted above), it is the responsibility of the base class (in this case _Nested_exception to initialize Base, not the responsibility of your Derived class.

Therefore, an attempt is made to find a default constructor, which fails, because it is implicitly deleted, because there is another user-provided constructor, and the standard states:

If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted.

§12.1 [class.ctor]

Accordingly this code is correctly rejected by the compiler.

Upvotes: 1

CashCow
CashCow

Reputation: 31445

The issue is that when you have virtual inheritance, the last derived class is responsible for constructing the base.

In this case, the library is trying to create more classes that derive from yours and then cannot construct them.

When you remove the "virtual" inheritence, the final classes can derive from yours without an issue.

The implementation "code" that is hitting the error message...

template<typename _Except>
struct _Nested_exception : public _Except, public nested_exception 
{
  explicit _Nested_exception(_Except&& __ex)
  : _Except(static_cast<_Except&&>(__ex))
 { }
};

so it needs in its constructor to construct a Base as it is now the most derived class, and isn't able to dp so correctly.

Upvotes: 2

Related Questions