10
votes

All of GCC 4.8.4, 4.9.3, 5.3.0 pass the tests for std::exception (for any of -std=c++11/1y/14/1z/17 options, where available):

static_assert(std::is_nothrow_copy_constructible<std::exception>::value, "test exception");
static_assert(std::is_nothrow_copy_assignable   <std::exception>::value, "test exception");

Which is fine, since std::exception has noexcept special members (C++14 18.8.1):

namespace std {
  class exception {
  public:
    exception() noexcept;
    exception(const exception&) noexcept;
    exception& operator=(const exception&) noexcept;
    virtual ~exception();
    virtual const char* what() const noexcept;
  };
}

Unfortunately, all of the compilers above fail on the following static_asserts:

static_assert(std::is_nothrow_copy_constructible<std::runtime_error>::value, "test runtime_error");
static_assert(std::is_nothrow_copy_assignable   <std::runtime_error>::value, "test runtime_error");

The standard contains only the following about std::runtime_error in 19.2.6:

namespace std {
  class runtime_error : public exception {
  public:
    explicit runtime_error(const string& what_arg);
    explicit runtime_error(const char* what_arg);
  };
}

But nothing is said about the noexceptness of the other (implicitly declared special) members nor the storage implementation requirements of what_arg.

The standard (C++14) says the following in 15.4/14:

An inheriting constructor (12.9) and an implicitly declared special member function (Clause 12) have an exception-specification. If f is an inheriting constructor or an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

And the following in 18.8.1/2:

Each standard library class T that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception.

Since std::runtime_error does not expose the implementation of the what_arg storage, we do not know whether it's (special) members are noexcept or not, so the noexceptness of std::runtime_error's copy constructor or copy assignment members are undecidable. Our only bet is 18.8.1 above.

Question 1/a) We consider std::runtime_error's copy constructor or copy assignment to be noexcept (1, 2). Is this true / state-of-the-art / best practice?

Question 1/b) Do not we need to explicitly state this in the standard? (Like in 18.8.2, Class bad_exception)

Question 1/c) Is it a bug in GCC that it fails the static_assert tests above?

Question 2) If the above deduction is wrong, could someone point me to the section(s) in the standard which state that std::runtime_error has noexcept copy constructor (and copy assignment)? (Or where it states that they are not.)

1
Are you sure gcc 5.3.0 does not pass the tests for std::runtime_error? It seems that gcc 5.2.0 passes the tests. - cpplearner
I tried with g++-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204 and it fails. - Norbert Bérci
Hmmmm, there might be some issues with my GCC 5.3.0 installation, since it has the following in /usr/include/c++/5.3.0/stdexcept: #if GLIBCXX_USE_CXX11_ABI || _GLIBCXX_DEFINE_STDEXCEPT_COPY_OPS runtime_error(const runtime_error&) _GLIBCXX_ssss_USE_NOEXCEPT; runtime_error& operator=(const runtime_error&) _GLIBCXX_USE_NOEXCEPT; #endif but it did not emit an error (note that I have modified the _GLIBCXX define) - Norbert Bérci
These special members are noexcept for llvm's libc++, and for VS-2015. melpon.org/wandbox/permlink/DzUHKeD4c4MXOvQH reports them noexcept for gcc's 5.1 and above. - Howard Hinnant
See LWG 1371. - Columbo

1 Answers

2
votes

Consider LWG 1371:

None of the exception types defined in clause 19 are allowed to throw an exception on copy or move operations, but there is no clear specification that the operations have an exception specification to prove it. Note that the implicitly declared constructors, taking the exception specification from their base class (ultimately std::exception) will implicitly generate a noexcept exception specification if all of their data members similarly declare noexcept operations. As the representation is unspecified, we cannot assume nonthrowing operations unless we explicitly state this as a constraint on the implementation.

[ Resolution proposed by ballot comment: ]

Add a global guarantee that all exception types defined in clause 19 that rely on implicitly declared operations have a non-throwing exception specification on those operations.

In the 2010 Batavia meeting, it was found that [exception]/2 "covered this":

Each standard library class T that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception.

Therefore it still isn't mandated that these special member functions are noexcept. And according to the way that implicit exception specifications are determined in [except.spec]/16, since an implementation can add both arbitrary parameters with default arguments and members, it's implementation-specific whether or not these special member functions are noexcept.