【问题标题】:why a constexpr expression of literal class type with a pointer subobject can't be a non-type template argument为什么带有指针子对象的文字类类型的 constexpr 表达式不能是非类型模板参数
【发布时间】:2023-03-14 03:10:01
【问题描述】:

查看the关于非类型模板参数的帖子后,我对该帖子中的示例感到困惑,我在这里引用示例:

struct VariableLengthString {
   const char *data_ = nullptr;
   constexpr VariableLengthString(const char *p) : data_(p) {}
   auto operator<=>(const VariableLengthString&) const = default;
};

template<VariableLengthString S>
int bar() {
   static int i = 0;
   return ++i;
}

int main() {
   int x = bar<"hello">(); // ERROR
}

帖子说“相关的措辞是[temp.arg.nontype]/2”,所以我看了一下那个规则,它限制了什么可以是非类型模板参数。

非类型模板参数的模板参数应该是模板参数类型的转换后的常量表达式

所以,我看了一下converted constant expression,它的定义在这里:

转换后的T类型常量表达式是一个表达式,隐式转换为T类型,其中转换后的表达式是常量表达式,而隐式转换序列只包含...

什么是常量表达式?这些规则在这里:

表达式 e 是核心常量表达式,除非根据抽象机的规则对 e 的求值将求值以下表达式之一:

(2.2) 调用除文字类的 constexpr 构造函数、constexpr 函数或普通析构函数的隐式调用之外的函数。

那么,类类型VariableLengthString是不是一个字面量类?是的,是的。这里有什么规则可以证明:

一个类型是文字类型,如果它是:
1. 可能是 cv 限定的 void;或
2. 标量类型;或
3. 参考类型;或
4. 字面量类型的数组;或
具有以下所有属性的可能具有 cv 限定的类类型:

  1. 它有一个微不足道的析构函数,
  2. 它要么是闭包类型、聚合类型,要么具有至少一个非复制或移动构造函数的 constexpr 构造函数或构造函数模板(可能继承自基类),
  3. 如果它不是联合,则它的所有非静态数据成员和基类都是非易失文字类型。

关键是,VariableLengthString类型的ojbects的这些类型的子对象都是字面量类型吗? VariableLengthString 类是否至少有一个 constexpr 构造函数?是的。因为这些规则:

算术类型、枚举类型、指针类型、指向成员类型([basic.compound])的指针、std​::​nullptr_t,以及这些类型的 cv 限定版本统称为 标量类型。

所以,子对象data_,它是指针类型。因此,它是标量类型,也是文字类型。满足项目符号3。constexpr VariableLengthString(const char *p)是一个constexpr构造函数吗?是的,它是,因为这些规则:

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

  1. 该类不应有任何虚拟基类;
  2. 每个参数类型都应该是文字类型;
  3. 每个非变体非静态数据成员和基类子对象都应该被初始化

对于constexpr VariableLengthString(const char *p),这三个规则都满足了。综上所述,VariableLengthString 类是一个字面量类型,VariableLengthString 类型的 constexpr 表达式可以用作非类型模板参数,因为它满足转换后的常量表达式的要求。为什么上面的代码格式不正确?如果我遗漏了什么,请帮我找出它们。

【问题讨论】:

  • "and the implicit conversion sequence contains only" 这句话的其余部分听起来很重要,因为您实际上使用的是隐式转换序列。
  • @NicolBolas 这可能是一个关键点,但是同样的事情也发生在该帖子中的示例 2 上,为什么该示例格式正确?如果 和隐式转换序列只包含将是一个关键点。
  • @NicolBolas 换句话说,在那个例子中,从 "char const(&)[6]" 到 "char const*" 是 FixedLengthString 的构造函数的参数类型,它是一个 "Array -to-pointer”转换和从“char const*”到FixedLengthString,这是一个用户定义的转换。所以,这里有两个转换。但是这个例子是格式正确的。
  • 那篇论文已经过时了:它所描述的问题已由P1907R1 修复(尽管讨论很少)。
  • 有一个非常简单的解决方法:godbolt.org/z/RTpfHu

标签: c++ language-lawyer c++20


【解决方案1】:

代码格式不正确,因为the standard says so:

对于引用或指针类型的非类型模板参数,或对于类类型或其子对象的非类型模板参数中的每个引用或指针类型的非静态数据成员, 引用或指针值不应引用或作为(分别)的地址:

...

  • 字符串文字

添加了重点。 C++17 及之前的版本不允许您将指向文字的指针用作 NTTP。因此,C++20 不允许您通过 NTTP 的类成员走私指向文字的指针。

【讨论】:

  • 我找不到句子“或者对于类类型或其子对象的非类型模板参数中的每个引用或指针类型的非静态数据成员”,它们在哪里标准?
  • @jackX:我从错误的标准中复制了它。用户定义的 NTTP 不是 C++17 的一部分;它们是 C++20 的一部分。
  • 嗨,NicolBolas,我认为答案在这里expr.const/5.3,模板参数用作prvalue,所以需要5.3,子对象'data_'不满足项目符号5.2,所以,它不是常量表达式,而是核心常量表达式
  • @jackX:这与提案所讨论的内容无关。表达式并没有阻止它成为常量表达式。这一切都是为了成为一个NTTP。这就是为什么它不合法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-21
  • 2016-08-23
  • 1970-01-01
  • 1970-01-01
  • 2016-03-01
  • 2013-03-30
相关资源
最近更新 更多