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.
Related
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.
I just a have a problem in understanding when the compiler marks the constructor as constexpr.
If I write the following program:
struct S{ S() {}; }
constexpr S s{ };
Does this mean that the default constructor is marked as constexpr?
An implicitly-defined constructor is a constructor defined by the compiler implicitly when some contexts are encountered (see below). But, an explicitly-defined constructor is a constructor defined by the user, not by the compiler.
Now per [class.default.ctor]/4:
A default constructor that is defaulted and not defined as deleted is
implicitly defined when it is odr-used ([basic.def.odr]) to create an
object of its class type ([intro.object]), when it is needed for
constant evaluation ([expr.const]), or when it is explicitly defaulted
after its first declaration. The implicitly-defined default
constructor performs the set of initializations of the class that
would be performed by a user-written default constructor for that
class with no ctor-initializer and an empty compound-statement. If
that user-written default constructor would be ill-formed, the program
is ill-formed. If that user-written default constructor would satisfy
the requirements of a constexpr constructor ([dcl.constexpr]), the
implicitly-defined default constructor is constexpr [..]
This paragraph just tells you that the non-deleted defaulted default constructor is implicitly-defined when it's odr-used or needed for constant evaluation or explicitly-defaulted after its first declaration.
Also, it tells you that that implicitly-defined constructor is the same as the user-written default constructor with an empty body and no member-initializer-list.
Then, it tells you that it's defined as constexpr if its corresponding user-written default constructor satisfies all of the [dcl.constexpr]/3 conditions.
That's, an implicitly or explicitly defaulted constructor will be implicitly-defined as constexpr if all requirements of [dcl.constexpr]/3 are met. On other hand, neither explicitly-defined nor explicitly-declared constructor is implicitly-defined as constexpr even if it satisfies all of [dcl.constexpr]/3 that's because you explicitly defined it. But if you explicitly mark it as constexpr, it will be a constexpr constructor, meanwhile, it shall satisfy all of [dcl.constexpr]/3 conditions.
So in your example:
struct S{ S() {}; }
constexpr S s{ };
That's ill-formed just because S is not a literal type and you're trying to call a non-constexpr constructor in a constant expression context which is not allowed per [const.expr]/(5.2)
A constructor is only (potentially) implicitly constexpr if either the whole constructor itself is implicitly declared or if it is defaulted with = default on its first declaration.
You are manually declaring the constructor and you are not defaulting it, so it will only be constexpr if you add the constexpr specifier to the declaration.
The shown constructor is therefore not constexpr and as a consequence constexpr S s{ }; will fail to compile because the initialization would call a non-constexpr constructor which isn't allowed in a constant expression. constexpr on a variable declaration does however require the initialization of the variable (including the evaluation of the initializer(s)) to be a constant expression.
The other answers give details about why the constructor in your snippet is not constexpr, and that's good to know.
However, I think, given how the question is phrased, that another answer is required.
Does declaring a constexpr object marks the constructor as constexpr
This means that you are thinking of the idea that the way you declare an object can affect the definition of its class.
That is not the case: once you write down the definition of a class, that's the definition, at it won't be altered by whatever objects of that class you declare/define.
In more complex scenario, when templates are involved, the code generated for a template class can depend on the objects you create; but the point is that you'd be talking of a templates class, whereas your question is about a class.
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.
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.
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. ...