5

Here is my code:

class agg_t1{
    int x;      // private non-static data menber
};
class agg_t2{
    agg_t2(){}      // user-provided constructor
};
constexpr void ce1(agg_t1 arg){};       // OK
constexpr void ce2(agg_t2 arg){};       // ERROR:  parameter type 'agg_t2' is not a literal type 

According to dcl.constexpr:

The definition of a constexpr function shall satisfy the following requirements: ...

  • each of its parameter types shall be a literal type; ...

And basic#types.general-10:

A type is a literal type if it is: ...

  • it is either a closure type, an aggregate type, or ...

I understand the reason why agg_t2 is not a literal type is that, it violates the rule dcl.init.aggr#1.1:

An aggregate is an array or a class with ...

  • no user-declared or inherited constructors ...

and I think agg_t1 may not be a literal type because it violates the rule dcl.init.aggr#1.1 too:

An aggregate is an array or a class with ...

  • no private or protected direct non-static data members ...

However... the compiler result tells me I was wrong about the assumption for agg_t1.

My question is:

If agg_t1's private data member x makes it non-aggregate type ,then why the agg_t1 type is permitted in constexpr function definition of ce1?

absuu
  • 340
  • 1
  • 11
  • 2
    There were words that come after "aggregate type" in the definition of what types are literal types. Those words might be important. – Nicol Bolas Dec 28 '21 at 04:59
  • 1
    Your argument is that neither `agg_t1` nor `agg_t2` are aggregates, despite their cleverly-chosen names. You have yet to eliminate the other possibilities by which they might be literal types (closure type and whatever you cut off after the "or" -- as with the "or" operator, proving that one operand to `||` is false does not make the entire expression false.) – JaMiT Dec 28 '21 at 05:11
  • @NicolBolas Can I have those words that you mentioned? I read it agagin but I haven't found any information useful for myself – absuu Dec 28 '21 at 05:40
  • 2
    "... or has at least one constexpr constructor or constructor template (possibly inherited ...) that is not a copy or move constructor," – Jiří Baum Dec 28 '21 at 05:47
  • 2
    That part after or: "... or has at least one constexpr constructor or constructor template". It so happens that an implicitly-declared default constructor for `agg_t1` is `constexpr`, per [**\[class.default.ctor\]/4**](https://eel.is/c++draft/class.default.ctor#4) – Igor Tandetnik Dec 28 '21 at 05:51
  • @IgorTandetnik According to the [test](https://godbolt.org/z/zYvPj93fP), which gives *"...because the implicit declaration is not 'constexpr'"*. Due to this error, I don't think that implicitly-declared default constructor is "consexpr". Is there anything I've missed? – absuu Dec 28 '21 at 07:48
  • @JiříBaum So should I consider it as the situation with " or has at least one constexpr constructor "? But I've tried to declare that "implicit default constructor" as *constexpr*, But failed with errors which mentioned on the previous comment. – absuu Dec 28 '21 at 07:55
  • 1
    Add `-std=c++20` switch, then your test [compiles](https://godbolt.org/z/Yvozv9xe4). – Igor Tandetnik Dec 28 '21 at 13:37
  • @IgorTandetnik Thanks! So can I consider it as a bug for the versions prior to CPP20? – absuu Dec 29 '21 at 03:58
  • I must admit I'm not sure why your original example compiles even with C++17. I understand why `constexpr` test fails with C++17 - C++17 requires that `constexpr` constructor initialize all non-static members; C++20 removes this requirement. But indeed, `agg_t1` doesn't appear to be a literal type under C++17 rules, so not clear why `ce1` is accepted there. – Igor Tandetnik Dec 29 '21 at 04:57

1 Answers1

0

If agg_t1's private data member x makes it non-aggregate type, then why the agg_t1 type is permitted in constexpr function definition of ce1?


C++20

agg_t1 is indeed not an aggregate class due to its private data member, however whilst aggregateness can be one of the sufficient requirements for class type to be a literal type, it is not a necessary one. Note the or condition of [basic.types.general]/10.5.2 [emphasis mine]:

A type is a literal type if it is:

  • [...]
  • a possibly cv-qualified class type that has all of the following properties:
    • [...]
    • it is either a closure type ([expr.prim.lambda.closure]), an aggregate type ([dcl.init.aggr]), or has at least one constexpr constructor or constructor template (possibly inherited ([namespace.udecl]) from a base class) that is not a copy or move constructor, [...]

As per [class.default.ctor]4:

[...] If that user-written default constructor would satisfy the requirements of a constexpr constructor ([dcl.constexpr]), the implicitly-defined default constructor is constexpr [...]

and [dcl.constexpr]/3 and [dcl.constexpr]/4:

/3 The definition of a constexpr function shall satisfy the following requirements:

  • [...]
  • if the function is a constructor or destructor, its class shall not have any virtual base classes; [...]

/4 The definition of a constexpr constructor whose function-body is not = delete shall additionally satisfy the following requirements:

  • for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
  • [...]

the implicitly-defined default constructor of agg_t1 is constexpr, and thus [basic.types.general]/10.5.2 does not disqualify agg_t1 from being a literal type in C++20.


C++17

In C++17 the implicitly-defined default constructor of agg_t1 is not constexpr, as it violates [dcl.constexpr]/4.5:

The definition of a constexpr constructor shall satisfy the following constraints:

  • [...]

In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:

  • [...]
  • /4.5 every non-variant non-static data member and base class sub-object shall be initialized ([class.base.init]);

And indeed, whilst the following is rejected by both Clang and GCC for -std=c++17:

class A {
    constexpr A() = default;  // error: cannot be constexpr
private:
    int x;
};

the following is accepted:

// OK: B is a literal type.
class B {
    constexpr B() = default;  // OK
private:
    int x{};
};
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thanks for the patient explanations. While being convinced by most of it, I doubt this: *"In C++17... agg_t1 is not constexpr...it violates..."*. [Here](https://godbolt.org/z/8K9fsdsrW)(11,14,17,20) shows that `ce1` accepts the type `agg_t1`, which has only one private data member, meaning that `agg_t1` must be a valid *LiteralType*. Since we all agree that `agg_t1` is not an "aggregate", according to the requirements(basic.types.general#10.5) of *LiteralType*, then if its implicitly-defined constructor is still not "constexpr", what else it could be that makes it become a valid *LiteralType*? – absuu Dec 29 '21 at 14:48
  • @absuu Afaict this seems like a standard defect. And indeed, open issue [CWG 1360](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1360) writes: _"According to 11.4.5 [class.ctor] paragraph 6, a defaulted default constructor is constexpr if the corresponding user-written constructor would satisfy the constexpr requirements. However, the requirements apply to the definition of a constructor, and a defaulted constructor is defined only if it is odr-used, leaving it indeterminate at declaration time whether the defaulted constructor is constexpr or not."._ – dfrib Dec 29 '21 at 15:12