How to make default constructor defined outside the class noexcept? - c++

I know that a constructor marked as =default will "try" to be noexcept whenever possible. However, if I define it outside the class, it is not noexcept anymore, as you can see from this code:
#include <iostream>
#include <utility>
#include <type_traits>
struct Bar
{
Bar() = default;
Bar(Bar&&) = default; // noexcept
};
struct Foo
{
Foo() = default;
Foo(Foo&&);
};
// moving the definition outside makes it noexcept(false)
Foo::Foo(Foo&&) = default; // not noexcept anymore
int main()
{
Foo foo;
Bar bar;
std::cout << std::boolalpha;
// checks
std::cout << std::is_nothrow_move_constructible<Bar>::value << std::endl;
std::cout << std::is_nothrow_move_constructible<Foo>::value << std::endl;
}
How can I define such a =default constructor outside a class and make it noexcept? And why is such a constructor noexcept(false) if defined outside the class? This issue arises when implementing PIMPL via smart pointers.

I realized now that I can do this, it didn't cross my mind until now:
struct Foo
{
Foo() = default;
Foo(Foo&&) noexcept;
};
Foo::Foo(Foo&&) noexcept = default; // now it is noexcept
Still the second question Why is it noexcept(false) by default? applies.

The rules governing the exception specification for your two examples are covered in §8.4.2/2 [dcl.fct.def.default]
... If a function is explicitly defaulted on its first declaration,
— it is implicitly considered to be constexpr if the implicit declaration would be,
— it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4), and
— ...
Bar's move constructor is noexcept(true) because in §15.4/14 [except.spec]
An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is 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 shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions
if every function it directly invokes allows no exceptions.
The rules in §8.4.2/2 do not apply to special member functions that have been explicitly defaulted after the initial declaration, except destructors, which are special cased in §12.4/3 to be noexcept(true) unless you declare it noexcept(false) or the destructors of one of the data members or base classes can throw.
Thus, unless you specify Foo(Foo&&) to be noexcept(true), it is assumed to be noexcept(false).
The reason you needed to add the noexcept specification to both the declaration and later explicit default declaration is found in §15.4
3 Two exception-specifications are compatible if:
— both are non-throwing (see below), regardless of their form,
— ...
4 If any declaration of a function has an exception-specification that is not a noexcept-specification allowing all exceptions, all declarations, including the definition and any explicit specialization, of that function shall have a compatible exception-specification. ...

Related

constructor is implicitly deleted because its exception-specification does not match the implicit exception-specification

Can anyone explain me why using constructor marked as default will have the error but the other one doesn't have. My understanding is they are very much similar.
Thanks.
class B {
public:
B() {
throw int(42);
}
};
class A {
public:
A() noexcept = default; // error: use of deleted function ‘A::A()’
// note: ‘A::A() noexcept’ is implicitly deleted because
// its exception-specification does not match the implicit exception-specification ‘’
//A() noexcept : m_b{}{}
B m_b;
};
Originally, the declaration of the constructor was already ill-formed, the idea being that the defaulted function ought to behave identically to the implicit declaration. Your explicit implementation has no such restriction: noexcept functions are allowed to call potentially throwing functions, and the behavior is even well-defined if they throw.
In C++14 it was relaxed to be merely deleted in that case. Later it was realized that even that was too restrictive (and difficult to implement correctly), so it was relaxed again: now the exception specification can be anything (but there’s still an automatic one for defaulted functions).
Both of these changes were retroactive, so you’ll see a dependence on compiler version more than language version.
Note that none of this depends on the actual throw in B::B: that constructor is potentially throwing regardless.
Implicit default constructor exception specification differs from your declaration of explicit default constructor. Here's the relevant (although outdated) part of the standard:
If a function that is explicitly defaulted is declared with an exception-specification that is not compatible (15.4) with the exception specification on the implicit declaration, then
if the function is explicitly defaulted on its first declaration, it is defined as deleted;
otherwise, the program is ill-formed.
In 2019 the proposal was made to change this behaviour. As I understand in c++20 your code will run just fine.

Declaring defaulted assignment operator as constexpr: which compiler is right?

Consider
struct A1 {
constexpr A1& operator=(const A1&) = default;
~A1() {}
};
struct A2 {
constexpr A2& operator=(const A2&) = default;
~A2() = default;
};
struct A3 {
~A3() = default;
constexpr A3& operator=(const A3&) = default;
};
GCC and MSVC accept all three structs. Clang rejects A1 and A2 (but accepts A3), with the following error message:
<source>:2:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr A1& operator=(const A1&) = default;
^
<source>:6:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr A2& operator=(const A2&) = default;
^
2 errors generated.
(live demo)
Which compiler is correct, and why?
I think all three compilers are wrong.
[dcl.fct.def.default]/3 says:
An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.
When is the copy assignment operator implicitly declared constexpr? [class.copy.assign]/10:
The implicitly-defined copy/move assignment operator is constexpr if
X is a literal type, and
[...]
Where a literal type is, from [basic.types]/10:
A type is a literal type if it is:
[...]
a possibly cv-qualified class type that has all of the following properties:
it has a trivial destructor,
[...]
A1 doesn't have a trivial destructor, so its implicit copy assignment operator isn't constexpr. Hence that copy assignment operator is ill-formed (gcc and msvc bug to accept).
The other two are fine, and it's a clang bug to reject A2.
Note the last bit of [dcl.fct.def.default] that I quoted. You don't actually have to add constexpr if you're explicitly defaulting. It would be implicitly constexpr where that is possible.
The C++17 standard states:
15.8.2 Copy/move assignment operator [class.copy.assign]
...
10 A copy/move assignment operator for a class X that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.2) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration. The implicitly-defined copy/move assignment operator is constexpr if
(10.1) — X is a literal type, and
(10.2) — the assignment operator selected to copy/move each direct base class subobject is a constexpr function, and
(10.3) — for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is a constexpr function.
The copy-assignment operator satisfies the above requirements in two of the cases. In the first case, we have a non-literal type because of the non-trivial destructor.
So I believe Clang is wrong to reject the code in the second case.
There is a bug filed with Clang titled: Defaulted destructor prevents using constexpr on defaulted copy/move-operator which shows the same symptoms as the code in the OP.
The comments from the bug report state:
When defaulted destructor is commented out (i.e. not user declared), then errors cease to exist.
and
The problem also goes away if you declare the destructor before the copy assignment operator.
This is true of the code in the question as well.
As #YSC points out, another relevant quote here is:[dcl.fct.def.default]/3 which states:
An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

C++ constexpr inheriting constructor

The following code compiles with GCC 8.2 but not with Clang 6.0.1:
// A struct named Foo.
struct Foo
{
// Data member of type 'int'.
int val;
// Default constructor (constexpr).
constexpr Foo() noexcept : val(0) {}
};
// A struct named Bar.
struct Bar : Foo
{
// Make use of the constructors declared in Foo.
using Foo::Foo;
// A constructor taking an object of type Foo.
// COMMENTING THIS CONSTRUCTOR SOLVE THE COMPILATION ISSUE.
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
};
// A struct named Test.
struct Test
{
// Data member of type 'Bar'.
Bar bar;
// A defaulted default constructor.
constexpr Test() noexcept = default;
};
// Main function.
int main() { return 0; }
Clang fails with the following message:
error: defaulted definition of default constructor is not constexpr
constexpr Test() noexcept = default;
I would like to understand why Clang is rejecting this code.
It looks like clang is relying on pre C++17 wording from C++14 section [class.inhctor]p3:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template ([temp.over.link]) in the complete class where the using-declaration appears. [ Note: Default arguments are not inherited. An exception-specification is implied as specified in [except.spec]. — end note ]
So in C++14:
using Foo::Foo;
means Bar does not inherit Foo's default constructor and Bar does not have a default constructor since it is inhibited by your declaration of:
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
Adding a default constructor to Bar fixes the problem see it live:
constexpr Bar() = default ;
The wording was changed in C++17 with the paper p0136r1: Rewording inheriting constructors (core issue 1941 et al) which was can see was accepted from Changes between C++14 and C++17 DIS
The following papers were moved at committee meetings, but their contents are too specific to call out as separate features: N3922, N4089, N4258, N4261, N4268, N4277, N4285, P0017R1, P0031R0, P0033R1, P0074R0, P0136R1, P0250R3, P0270R3, P0283R2, P0296R2, P0418R2, P0503R0, P0509R1, P0513R0, P0516R0, P0517R0, P0558R1, P0599R1, P0607R0, P0612R0
we can see p0136r1 removed [class.inhctor]:
Remove 12.9 class.inhctor, "Inheriting constructors".
I don't see any wording in p0136r1 that would restrict this case any more. The list of defect reports does not specifically cover this case but the wording changes seem consistent.
So it looks like gcc is correct here and we have a potential clang bug.
gcc 7 release notes
We also obtain a diagnostic in gcc pre 7.x (see it live). If we look at the gcc 7 release notes we see:
The default semantics of inherited constructors has changed in all modes, following P0136. Essentially, overload resolution happens as if calling the inherited constructor directly, and the compiler fills in construction of the other bases and members as needed. Most uses should not need any changes. The old behavior can be restored with -fno-new-inheriting-ctors, or -fabi-version less than 11.
Which seems to confirm the initial conclusion. If we use -fno-new-inheriting-ctors with a slightly modified version of your program it no longer compiles which backs up this was changed with P0136.
In C++14, default constructors can not be inherited.
§12.9 [class.inhctor] (emphasis mine)
3 For each non-template constructor in the candidate set of
inherited constructors other than a constructor having no parameters
or a copy/move constructor having a single parameter, a constructor is
implicitly declared with the same constructor characteristics unless
there is a user-declared constructor with the same signature in the
complete class where the using-declaration appears or the constructor
would be a default, copy, or move constructor for that class. ...
This basically means, that for your class Bar, the ctor will be implicitly defined - and means using Foo::Foo is not doing anything meaningful.
However, as you are having a separate constructor for Bar, this prevents the implicit definition of a default constructor.
The reason this works when you comment out your separate constexpr Bar(Foo const obj) ctor is because of
5 [ Note: Default and copy/move constructors may be implicitly
declared as specified in 12.1 and 12.8. —end note ]
§12.1/5 [class.ctor]
... If that user-written default constructor would satisfy the
requirements of a constexpr constructor (7.1.5), the
implicitly-defined default constructor is constexpr. ...
So, the implicitly declared constructor is declared as constexpr, which makes your code work and compile as expected.
You can remedy the issue by just explicitly defaulting the default ctor like this:
constexpr Bar() noexcept = default;
You can also take a look at Constexpr class: Inheritance?
The problem there is a bit different, but very similar to what you are seeing.
Sadly, I am unable to find the relevant parts in the C++17 standard. I assume the reasoning is the same, but can't find the reference to be 100% sure.

=default in declaration vs definition

I know that instead of writing:
class A {
public:
A(A&&) noexcept = default;
};
One should better write
class A {
public:
A(A&&) noexcept;
};
inline A::A(A&&) noexcept = default;
The reasons I've heard are:
It avoids the constructor becomes deleted. Compiler will give an error if it is unable to define the function.
The move constructor is declared noexcept even if some of the member fields' move constructor are not annotated with noexcept.
Could someone explain a bit more about the theory behind the differences?
Only declaration is used to describe the class/method, so when doing
class A {
public:
A(A&&) noexcept;
};
You might even implement A::A(A&&) as you want (definition can be in different TU)
When you implement it with:
A::A(A&&) noexcept = default;
Compiler has to generate the method (it cannot tell if it is implicitly deleted as declaration precise method exists), and provides diagnostic if it can't.
But when you declare it inside the class:
class A {
public:
A(A&&) noexcept = default;
};
It is "part" of declaration. so it might be implicitly deleted (because of member or base class).
Same apply for noexcept.
An other advantage to put definition in dedicated TU, it that definition of required dependencies can be only in that TU, instead of each place where the method would be generated. (Useful for pimpl idiom for example).
One disadvantage of split definition and declaration is that the method is now "user provided", that may affect traits as trivially_constructible/copyable/...
The behavior is covered in [dcl.fct.def.default]p3 which says:
If a function that is explicitly defaulted is declared with a noexcept-specifier that does not produce the same
exception specification as the implicit declaration (18.4), then
(3.1) — if the function is explicitly defaulted on its first declaration, it is defined as deleted;
(3.2) — otherwise, the program is ill-formed.
Note the wording changes in C++20 but the intent is the same for this case. I find the C++17 wording simpler to grok.
For example given:
struct S {
S( S&& ) noexcept(false) = default;
};
The move constructor is defined as deleted since due to [except.spec]p7:
An implicitly-declared constructor for a class X, or a constructor without a noexcept-specifier that is defaulted
on its first declaration, has a potentially-throwing exception specification if and only if any of the following
constructs is potentially-throwing:
(7.1) — a constructor selected by overload resolution in the implicit definition of the constructor for class X to
initialize a potentially constructed subobject, or
(7.2) — a subexpression of such an initialization, such as a default argument expression, or,
(7.3) — for a default constructor, a default member initializer.
none of the cases hold.
If we got back to [dcl.fct.def.default]p3 it says otherwise the program is ill-formed. Ill-formed programs require a diagnostic so if we modify the first example as follows (see it live):
struct S {
S( S&& ) noexcept(false) ;
private:
int i;
};
S::S( S&&) noexcept(false) = default ;
it will produce a diagnostic e.g.:
error: function 'S::S(S&&)' defaulted on its redeclaration with an exception-specification that differs from the implicit exception-specification 'noexcept'
S::S( S&&) noexcept(false) = default ;
^
Note clang bug related to this case, it seems they are not following Defect Report 1778.
You may want to note Declaring a function as defaulted after its first declaration which covers some the optimization/interface issues.

What is the exception specification for a defaulted virtual destructor in C++11?

Suppose I have:
class Foo
{
public:
virtual ~Foo()=default;
};
What is the exception-specification on the defaulted destructor? Is the defaulted destructor equivalent to:
virtual ~Foo() {};
or
virtual ~Foo() throw() {};
or
virtual ~Foo() noexcept {};
Section 15.4 of the C++11 standard says it depends on the exception specifications of the functions directly invoked by the destructor's implicit definition. In this case there are no members, and no base classes, so AFAIK there are no functions directly invoked by the implicit destructor. Is this an ambiguity (or omission) in the standard?
It matters, of course, because if it implicitly has throw(), then all subclasses must declare their destructors with throw(). Don't tell me it's a bad idea to throw exceptions in destructors, I know that. I deal with lots of legacy code where exception specs were not used at all.
As a point of information, when I tried:
class SubFoo : public Foo
{
public:
virtual ~SubFoo();
};
I got an error (mismatched exception specs) in GCC 4.4 (although I admit I may not have had the right command line switches), but not in XCode 4.3 using the "11" compilers.
Back up to earlier in the same sentence (§15.4/14):
...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;..."
Therefore, if ~Foo doesn't invoke any functions, it has an implicit declaration that allows no exceptions to be thrown.
According to §15.4/3:
Two exception-specifications are compatible if:
both are non-throwing (see below), regardless of their form,
That's the case here, so it doesn't really matter whether the declaration is throw() or noexcept -- the two are compatible in any case.
The standardese starts nicely in C++11 §8.4.2/2,
If a function is explicitly defaulted on its first declaration,
— it is implicitly considered to be constexpr if the implicit declaration would be,
— it is implicitly considered to have the same exception-specification as if it had been implicitly declared (15.4), …
But then, over in C++11 §15.4/14, the logic rapidly devolves,
An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is 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 shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.
In the standard's meaning of "allow" it is about explicitly allowing, through an exception specification.
If f calls two functions, one of which specifies and therefore allows T, and one of which allows all exceptions, then f must both specify T and allow all exceptions, which isn’t possible.
So this definitely looks like a defect in the standard.
I found a related Defect Report, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1351.
However, it looks like this area is just a Big Mess. :-(