【问题标题】:The Requirement for "Literal Type" in "constexpr" Functions“constexpr”函数中“文字类型”的要求
【发布时间】:2021-12-28 04:52:08
【问题描述】:

这是我的代码:

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 

根据dcl.constexpr

constexpr 函数的定义应满足以下要求:...

  • 它的每个参数类型都应该是一个文字类型; ...

还有basic#types.general-10

一个类型是文字类型,如果它是:...

  • 它可以是闭包类型、聚合类型,或者...

我理解agg_t2 不是文字类型的原因是它违反了dcl.init.aggr#1.1 的规则:

聚合是一个数组或一个类...

  • 没有用户声明或继承的构造函数...

我认为agg_t1 可能不是文字类型,因为它也违反了规则dcl.init.aggr#1.1

聚合是一个数组或一个类...

  • 没有私有或受保护的直接非静态数据成员...

但是...compiler result 告诉我我对 agg_t1 的假设是错误的。

我的问题是:

如果agg_t1的私有数据成员x使其成为非聚合类型,那么为什么constexprce1的函数定义中允许agg_t1类型?

【问题讨论】:

  • 在什么类型是文字类型的定义中,“聚合类型”之后有一些词。这些话可能很重要。
  • 您的论点是 agg_t1agg_t2 都不是聚合,尽管它们的名称是经过巧妙选择的。您尚未消除它们可能是文字类型的其他可能性(闭包类型以及您在“或”之后切断的任何内容 - 与“或”运算符一样,证明|| 的一个操作数为假并不使整个表达式为假。)
  • "...或至少有一个 constexpr 构造函数或构造函数模板(可能继承...)不是复制或移动构造函数,"
  • or 之后的那部分:“... or 至少有一个 constexpr 构造函数或构造函数模板”。碰巧agg_t1 的隐式声明的默认构造函数是constexpr,根据[class.default.ctor]/4
  • 添加-std=c++20开关,然后你的测试compiles

标签: c++ language-lawyer constexpr constant-expression constexpr-function


【解决方案1】:

如果agg_t1的私有数据成员x使其成为非聚合类型,那么为什么constexprce1函数定义中允许agg_t1类型?


C++20

agg_t1 确实不是聚合类,因为它的私有数据成员,但是聚合性可以是类类型作为文字的足够要求之一类型,它不是一个必要的。注意[basic.types.general]/10.5.2 [emphasis mine] 的 or 条件:

一个类型是文字类型,如果它是:

  • [...]
  • 可能具有 cv 限定的类类型具有以下所有属性
    • [...]
    • 它是要么闭包类型([expr.prim.lambda.closure]),聚合类型([dcl.init.aggr]),或至少有一个constexpr 构造函数或构造函数模板(可能继承 ([namespace.udecl]) 来自基类)不是复制或移动 构造函数,[...]

根据[class.default.ctor]4

[...] 如果用户编写的默认构造函数满足 constexpr 构造函数 ([dcl.constexpr]) 的要求, 隐式定义的默认构造函数是 constexpr [...]

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

/3 constexpr 函数的定义应满足以下条件 要求:

  • [...]
  • 如果函数是构造函数或析构函数,其类不应有任何虚拟基类; [...]

/4 constexpr 构造函数的定义,其函数体为 not = delete 还应满足以下要求:

  • 对于非委托构造函数,选择用于初始化非静态数据成员的每个构造函数和基类子对象应 成为 constexpr 构造函数;
  • [...]

agg_t1 的隐式定义的默认构造函数是 constexpr,因此 [basic.types.general]/10.5.2 不会取消 agg_t1 在 C++20 中作为文字类型的资格。


C++17

在 C++17 中,隐式定义的 agg_t1 的默认构造函数不是 constexpr,因为它违反了 [dcl.constexpr]/4.5

constexpr 构造函数的定义应满足以下条件 约束:

  • [...]

另外,它的函数体要么是 = delete,要么是 满足以下约束:

  • [...]
  • /4.5 每个非变体非静态数据成员和基类子对象都应该被初始化([class.base.init]);

事实上,虽然 Clang 和 GCC 都拒绝了 -std=c++17 的以下内容:

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

以下内容被接受:

// OK: B is a literal type.
class B {
    constexpr B() = default;  // OK
private:
    int x{};
};

【讨论】:

  • 感谢耐心的解释。虽然大部分内容都让我信服,但我对此表示怀疑:“在 C++17 中... agg_t1 不是 constexpr...它违反了...”Here(11,14,17,20) 表明 ce1 接受类型 agg_t1,它只有一个私有数据成员,这意味着 agg_t1 必须是有效的 LiteralType。既然大家都同意agg_t1不是一个“聚合”,根据LiteralType的要求(basic.types.general#10.5),那么如果它的隐式定义的构造函数仍然不是“constexpr” ",还有什么可能使它成为有效的 LiteralType
  • @absuu Afaict 这似乎是一个标准缺陷。事实上,公开问题CWG 1360 写道:“根据 11.4.5 [class.ctor] 第 6 段,如果相应的用户编写的构造函数满足 constexpr 要求,则默认的默认构造函数是 constexpr。但是,要求适用于构造函数的定义,并且默认构造函数仅在使用 odr 时才定义,在声明时不确定默认构造函数是否为 constexpr。"。
猜你喜欢
  • 2020-12-25
  • 2020-12-13
  • 1970-01-01
  • 2017-04-06
  • 2021-04-20
  • 1970-01-01
  • 2020-11-02
  • 2014-03-14
  • 1970-01-01
相关资源
最近更新 更多