I'll limit the scope to a default constructor, as in the question's code and tag. For the most part, you'll get the same effect since = default;
loosely means "give me the compiler-generated one". It's important to note what exactly having no declaration does:
If there is
no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared
as defaulted. An implicitly-declared default constructor is an inline public member of its class.
If your declaration changes any of these, it will no longer be the exact same as the implicit one. In the standard, Dog() = default;
is a user-declared constructor, but not a user-provided constructor. There are a couple small differences between having a user-declared default constructor and having no constructor.
As mentioned, aggregates were fixed:
struct not_agg {
not_agg() = delete;
int x;
};
Before the fix, such a class could be created via aggregate initialization: not_agg{}
. Naturally, this also extends to = default;
. Per [dcl.init.aggr]:
An aggregate is an array or a class with
- no user-declared or inherited constructors
There is also rationale given for this change in annex C:
Remove potentially error-prone aggregate initialization which may apply notwithstanding the
declared constructors of a class.
An interesting, but very tiny difference is that a class with a user-declared constructor is not allowed to have non-static data members of the same name as the class:
class c {
int c; // Okay
};
class c2 {
c2() = default;
int c2; // Error
};
This is due to [class.mem]/21:
In addition, if class T has a user-declared constructor, every non-static data member of class T shall have a name different from T.