19

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:

  1. It avoids the constructor becomes deleted. Compiler will give an error if it is unable to define the function.

  2. 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?

laike9m
  • 18,344
  • 20
  • 107
  • 140
  • I could verify that the first point is true : to catch compilation errors early, it's preferrable to used `default` in the class definition (see this https://godbolt.org/g/36iQb8). – Olivier Sohn Jul 14 '18 at 15:51
  • @bipll It is true. Believe me. – laike9m Jul 14 '18 at 15:56
  • My observation at this point, after having played with the code in the link above, is that when it's in the class definition, the default constructor will be generated no matter what, and when it's in the class declaration the default constructor will be generated "on demand", if used in the code. But I have no clue about the theory, wether this behaviour is specified or not. – Olivier Sohn Jul 14 '18 at 15:57
  • @bipll Probably the only reference I can find is here: https://akrzemi1.wordpress.com/2015/09/11/declaring-the-move-constructor/ – laike9m Jul 14 '18 at 16:10
  • This behavior is described in [dcl.fct.def.default]. If a special member function is declared with an explicit exception specification that does not match the one an implicitly declared special member would have, then (a) if `=default` is used on the first declaration (i.e. inside the class definition), it is taken to mean the same as `=delete`, or (b) if `=default` is used on a later declaration (outside the class definition), the program is ill-formed. Looks like the relevant wording will change from C++17 for C++20, but the exception-specification thing is true in both versions. – aschepler Jul 14 '18 at 21:44
  • Related: https://stackoverflow.com/q/8595471/103167 – Ben Voigt Jul 14 '18 at 22:50

2 Answers2

12

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/...

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Quality answer, taught me some stuff, upvoted. But generally, seems to me, you see it done in the class declaration. – Paul Sanders Jul 14 '18 at 21:09
  • 1
    "Compiler has to generate the method". This is the part I'm curious about, cause I don't know which specification contributes to it, or is it only about implementation? – laike9m Jul 15 '18 at 02:20
6

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Thanks for your answer! I think it explains the first point(deleted constrctor), and what about the second point? Also I don't quite get what "ill-formed programs require a diagnostic" means. – laike9m Jul 27 '18 at 15:00
  • @laike9m I was not entirely sure on the second point, I felt maybe "Declaring a function as defaulted after its first declaration" which I link to touched on it but not sure where 2 was going. As for ill-formed requiring diagnostic I cover some of that [in my answer here](https://stackoverflow.com/a/31685448/1708801). – Shafik Yaghmour Jul 27 '18 at 15:59