Reputation: 59811
This code fails to compile with gcc 4.8.2 (-std=c++11) but compiles with clang 3.4 (trunk) (-std=c++11):
#include <type_traits>
#include <vector>
struct X {
X& operator=(X&&) noexcept = default;
// adding noexcept this leads to an error in gcc, but works in clang:
// function ‘X& X::operator=(X&&)’ defaulted on its first
// declaration with an exception-specification that differs from the
// implicit declaration ‘X& X::operator=(X&&)’
std::vector<char> m;
};
// this assert holds, even without noexcept
static_assert(std::is_nothrow_move_assignable<X>::value,
"type-specification violation");
int main()
{
return 0;
}
The static_assert
is the interesting part on gcc: the defaulted move assignment will be noexcept
, but I cannot declare it that way.
A variant not involving vector
is:
template<bool b>
struct F {
F& operator=(F&&) noexcept(b) {return *this;}
};
struct X {
X& operator=(X&&) noexcept = default;
F<true> f;
};
What is the expected behavior here? Intuitively clang seems correct.
Upvotes: 15
Views: 3673
Reputation: 218780
The example not involving vector
should compile. clang is correct on this one.
The example involving vector
may or may not compile, depending on whether the std::lib vendor has marked the move assignment operator of vector
as noexcept
or not. The standard does not require this signature to be noexcept
. The standard does allow vendors to add noexcept
if the function will never throw.
libc++ marks the vector
move assignment operator as noexcept
if allocator_traits<allocator_type>::propagate_on_container_move_assignment::value
is true and is_nothrow_move_assignable<allocator_type>::value
is true (as a conforming extension). Both of these are true in the libc++ implementation for std::allocator<T>
.
Update
Pulling out the language lawyer hat on request.
<disclaimer>
I'm poking around in the half of the standard that is not my area of expertise.
</disclaimer>
Could you be more explicit on why the example should compile?
All quotes are from the latest C++1y working draft, N3797.
This specifies that an exception-specification is allowed on an explicitly defaulted special member:
8.4.2 Explicitly-defaulted functions [dcl.fct.def.default]/p2
2 An explicitly-defaulted function may be declared
constexpr
only if it would have been implicitly declared asconstexpr
, and may have an explicit exception-specification only if it is compatible (15.4) with the exception-specification on the implicit declaration.
This defines "compatible exception-specifications:"
15.4 Exception specifications [except.spec]/p3
3 Two exception-specifications are compatible if:
both are non-throwing (see below), regardless of their form,
both have the form
noexcept
(constant-expression) and the constant-expressions are equivalent, orboth are dynamic-exception-specifications that have the same set of adjusted types.
Bullet 2 covers your case.
This explains why the implicitly declared special member is noexcept
in your example:
15.4 Exception specifications [except.spec]/p14
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 ifT
is allowed by the exception-specification of a function directly invoked byf
’s implicit definition;f
allows all exceptions if any function it directly invokes allows all exceptions, andf
has the exception-specificationnoexcept(true)
if every function it directly invokes allows no exceptions.
Because every function invoked by an implicitly declared X& operator=(X&&)
allows no exceptions (i.e. the move assignment operator of F<true>
), this special member is noexcept(true)
.
I believe that nails it down.
Upvotes: 5