Why specifying "user-provided" constructor makes the class non-aggregate?

Viewed 101

I came across this example from cppreference:

...

struct T3
{
    int mem1;
    std::string mem2;
    T3() {} // user-provided default constructor
}

...

This example clearly show that the given constructor is user-provided constructor since it's not explicitly defaulted or deleted on its first declaration; per [dcl.fct.def.default]/5:

[..] A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration [..]

Now, Per [dcl.init.aggr]/1

An aggregate is an array or a class (Clause 11) with

  • (1.1) — no user-declared or inherited constructors (11.4.5),
  • (1.2) — no private or protected direct non-static data members (11.8),
  • (1.3) — no virtual functions (11.7.3), and
  • (1.4) — no virtual, private, or protected base classes (11.7.2).

It seems that my class satisfy all the above requirements to be an aggregate including the point (1.1) since the given constructor is not user-declared.

So why the following code it ill-formed (tested on g++12.2 with c++20 flag):

static_assert(std::is_aggregate<T3>::value); // fail

Why static assertion is failed?

2 Answers

Because a user-provided constructor is a user-declared constructor.

It is right there in the part you quote:

[..] A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration [..]

Only a user-declared constructor can be a user-provided constructor. A user-declared constructor is user-provided when it is not explicitly defaulted or deleted on its first declaration.

T3() {}         // I   user-provided, user-declared 
T3() = default; // II  user-declared, not user-provided
T3() = delete;  // III user-declared, not user-provided

Terms can be a little confusing. All 3 declare a default constructor (one that can be called without parameters). Only II is a defaulted constructor (definition is generated by compiler).

PS: Thanks to @John for clarifying that also

T3();     // IV user-declared, user-provided

Is user-declared and user-provided, even if later it might be defined via

T3::T3() = default;

Because according to the definition it is user-provided when (it is user-declared and) it is not explicitly defaulted or deleted on its first declaration.

Take the quote you quoted literally:

A function is user-provided if,

  • that function is user-declared function, and
  • that function is not explicitly defaulted on its first declaration, or
  • that function is not explicitly deleted on its first declaration.

Considering the quote above, and heading to [dcl.init.aggr]/1

An aggregate is an array or a class (Clause 11) with

(1.1) — no user-declared or inherited constructors (11.4.5), [..]

And since your class has a user-declared constructor, which is a user-provided constructor by definition, your assertion fails.

Related