Explicitly-defaulted and explicit default constructor vs. aggregate initialization

495 views
Skip to first unread message

David Krauss

unread,
May 17, 2014, 3:29:28 AM5/17/14
to std-dis...@isocpp.org
Explicitly defaulting a constructor on its member-declaration makes it not user-provided, hence permissible in an aggregate. Such a constructor may yet differ from the implicitly-declared one in exception-specification and the explicit function-specifier. Because aggregate initialization takes precedence in list-initialization, though, the constructor will be ignored in such contexts.

struct ag {
explicit ag() noexcept(false) = default;
};

ag q = {}; // OK despite explicitness
static_assert( noexcept( ag{} ), "" ); // OK despite exception-specification
ag r; /* Error: default constructor implicitly deleted because exception-specification does not match the implicit exception-specification */

I think it makes more sense to restrict aggregate list-initialization to non-empty lists. Moreover, any code relying on the strange behavior is already critically brittle because things go back to normal as soon as the class stops being an aggregate, for example if a virtual function or base is added.

Although this is a breaking change, it should be fixed. Aggregates are supposed to be as similar to their brethren as possible.

Richard Smith

unread,
May 18, 2014, 2:55:00 PM5/18/14
to std-dis...@isocpp.org
Core has discussed whether aggregate initialization or value initialization is preferred by {} at length, and I don't think the rules are likely to change (again) here. That said, there are other (perhaps better) ways we might fix this particular case. In particular, I'd suggest that the above struct should *not* be considered to be an aggregate -- perhaps the presence of any user-declared, deleted constructor should make a class a non-aggregate.

Ville Voutilainen

unread,
May 18, 2014, 3:06:17 PM5/18/14
to std-dis...@isocpp.org
I fail to see the problem. The recommendation has been for a while not to write
a noexcept-spec on a defaulted function unless you know what you're doing. The
code is asking for trouble, and is receiving a rather moderate amount of such
trouble.

Daniel Krügler

unread,
May 18, 2014, 3:09:23 PM5/18/14
to std-dis...@isocpp.org
2014-05-18 21:06 GMT+02:00 Ville Voutilainen <ville.vo...@gmail.com>:
> I fail to see the problem. The recommendation has been for a while not to write
> a noexcept-spec on a defaulted function unless you know what you're doing. The
> code is asking for trouble, and is receiving a rather moderate amount of such
> trouble.

One could argue similarly about the explicit noexcept(true)
specification in std:.atomic,

http://cplusplus.github.io/LWG/lwg-active.html#2165

but nonetheless this example was the reason for a corresponding
core-language change.

- Daniel

Ville Voutilainen

unread,
May 18, 2014, 3:18:10 PM5/18/14
to std-dis...@isocpp.org
Well, apparently the core change you speak of leads to the defaulted special
member function being deleted (instead of being ill-formed as a declaration)
when a noexcept-spec has a mismatch. Fiddling
with that any further seems unwise, so yes, David's example is ok for
aggregate-init
and ill-formed for a case that actually uses the default constructor.
That's just fine
by me.

Daniel Krügler

unread,
May 18, 2014, 3:21:20 PM5/18/14
to std-dis...@isocpp.org
I agree with the last part, but I interpreted your original response
as if you would find generally the decision for explicitly
noexcept-specifications unwise. I was (at least to some extend)
disagreeing with that as a general rule).

- Daniel

Ville Voutilainen

unread,
May 18, 2014, 3:24:18 PM5/18/14
to std-dis...@isocpp.org
On 18 May 2014 22:21, Daniel Krügler <daniel....@gmail.com> wrote:
>> Well, apparently the core change you speak of leads to the defaulted special
>> member function being deleted (instead of being ill-formed as a declaration)
>> when a noexcept-spec has a mismatch. Fiddling
>> with that any further seems unwise, so yes, David's example is ok for
>> aggregate-init
>> and ill-formed for a case that actually uses the default constructor.
>> That's just fine
>> by me.
> I agree with the last part, but I interpreted your original response
> as if you would find generally the decision for explicitly
> noexcept-specifications unwise. I was (at least to some extend)
> disagreeing with that as a general rule).


Right. I still do find explicit noexcept-specifications on defaulted special
member functions unwise, and I do find the one in atomic<T> unwise.
I would've preferred requiring nothrow-default-constructibility from T, but
I don't care strongly enough to complain about that after the change,
since it went unnoticed by me earlier.

David Krauss

unread,
May 18, 2014, 11:27:39 PM5/18/14
to std-dis...@isocpp.org
On 2014–05–19, at 3:06 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

On 18 May 2014 21:55, Richard Smith <ric...@metafoo.co.uk> wrote:

Core has discussed whether aggregate initialization or value initialization
is preferred by {} at length, and I don't think the rules are likely to
change (again) here.

What’s the (other) difference? Is there a DR or the like?

The next list initialization rule after aggregate initialization is "if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.” Empty aggregate initialization initializes piecewise by {}, which is piecewise value initialization except for std::initializer_list members (which are highly toxic) and members with disabled default constructors (the recursive case of the present question).

That said, there are other (perhaps better) ways we
might fix this particular case. In particular, I'd suggest that the above
struct should *not* be considered to be an aggregate -- perhaps the presence
of any user-declared, deleted constructor should make a class a
non-aggregate.


I fail to see the problem. The recommendation has been for a while not to write
a noexcept-spec on a defaulted function unless you know what you're doing. The
code is asking for trouble, and is receiving a rather moderate amount of such
trouble.

The explicit specifier may be considered as a separate issue. Actually I noticed the potential exception-specifier and added those examples as an afterthought. The subject line reflects the original issue.

Any difference between the user declaration and the implicit declaration potentially causes problems with aggregate initialization. Following Richard’s argument, explicit could also disqualify the class as an aggregate. This is a more drastic change, but I’m not sure there’s a use for aggregates that require at least one initializer, and as a means of making a class non-aggregate, it might be more elegant than any existing alternative.

David Krauss

unread,
May 19, 2014, 12:20:18 AM5/19/14
to std-dis...@isocpp.org

On 2014–05–19, at 3:24 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

Right. I still do find explicit noexcept-specifications on defaulted special
member functions unwise

The example was in error; the noexcept-specification was ill-formed. It was tolerated by GCC, which provided the message I put in the comment, but N3797 §8.4.2/2 makes it ill-formed. I’m not sure why GCC does this because the rule appears in C++11.

Anyway, an explicitly-deleted default constructor behaves the same way. An aggregate with a deleted default constructor is not default constructible, but something very like default construction is still possible when {} is the initializer.

struct no_default {
no_default() = delete;
};

struct ag {
int i;
no_default nd;
};

ag q = { 0 }; // OK: implicit {} initializer

Clang incorrectly diagnoses this as a call to the constructor.

David Krauss

unread,
May 19, 2014, 1:19:43 AM5/19/14
to std-dis...@isocpp.org
On 2014–05–19, at 11:27 AM, David Krauss <pot...@gmail.com> wrote:


On 2014–05–19, at 3:06 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

On 18 May 2014 21:55, Richard Smith <ric...@metafoo.co.uk> wrote:

Core has discussed whether aggregate initialization or value initialization
is preferred by {} at length, and I don't think the rules are likely to
change (again) here.

What’s the (other) difference? Is there a DR or the like?

Ah, now I find DR 1301 which links to 1324 and 1368.

1301 is about forbidding B() where B is a union type with a deleted default constructor. But B{} is still allowed. GCC accepts the DR motivating example with this minor change, and Clang provides a diagnosis which is incorrect. So I think it’s not really resolved.

1324 does not state a problem, so it’s hard to tell how it was resolved. The behavior did not change: it is still aggregate initialization. Was the motivating intent supposed to be that the missing default constructor makes the example ill-formed? It seems unresolved to me.

1368 seems unrelated. I’m not investigating now.

Reply all
Reply to author
Forward
0 new messages