The Requirement for "Literal Type" in "constexpr" Functions

Viewed 242

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?

1 Answers

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{};
};
Related