EDG与 GCC/Clang在常量表达式中对右值引用行为的差异

编程语言 2026-07-09

我写了以下程序,它被MSVC与 EDG接受,但被clang与 gcc拒绝。 演示

template<int N>
struct s{
  constexpr static const int&& n = N; 
  constexpr static       int&& p = N;
};
template<typename T> void f()
{
    static_assert(T::n == 1);
}
template<typename T> void g()
{
    static_assert(T::p == 1);
}

int main(){
   f<s<1>>(); //#1: works in all 4 compilers
   g<s<1>>(); //#2: works in edg & msvc but not in clang and gcc
}

可见,#2 被edg和 msvc接受,但被clang和 gcc拒绝。我的想法是 #2 应该无效,因为它是可修改的(例如我们可以写 s<1>::p = 3;,在所有编译器下都能编译通过)。但我想知道根据最新的C++标准,#2 的标准行为是什么。

解决方案

TL;DR

重要的不是引用对象的属性,而是它所引用的对象的属性及其生命周期。
它所引用的是从 N 物化的一个临时对象。
T::n 绑定一个不可变的 int const,其生命周期是 T::n 的生命周期:可以在常量表达式中使用。
T::p 绑定一个可变的 int:不能在常量表达式中使用。
因此GNU编译器是对的,EDG和 MSVC错误。

细节

static_assert(T::<whatever> == 1) T::<whatever> 的情形中,需进行左值到右值转换(lvalue-to-rvalue conversion):
https://eel.is/c++draft/conv.lval#1%5C
https://eel.is/c++draft/expr.arith.conv

特别是 https://eel.is/c++draft/expr.arith.conv#1.1

左值到右值转换([conv.lval])应用于每个操作数,所得的右值用于本节其余部分的替换原操作数。

那么从 T::<whatever> 的左值到右值转换得到的右值,是否可在常量表达式中使用?

https://eel.is/c++draft/expr.const#core-2

一个表达式E 是核心常量表达式,除非按照抽象机器的规则求值E 的过程会导致下列情况之一:

(2.9) 左值到右值的转换,除非它应用于
(2.9.1) 类型为cv std​::​nullptr_t的 glvalue,
(2.9.2) 指向在常量表达式中可用的对象的非易变glvalue,或
(2.9.3) 指向一个在E 的求值过程中开始生命周期的非易变对象的字面量类型的非易变glvalue;

(无关的要点略)

T::<whatever> 的左值到右值转换得到的是一个指向从 N 物化的临时对象的glvalue。
对于 T::n,其类型为 int const,对于 T::p,其类型为 int

这个临时对象在常量表达式中可用吗?
https://eel.is/c++draft/expr.const#init-9

一个对象或引用在点P 时可在常量表达式中使用,当它在点P 时可能在常量表达式中使用,并且在点P 可constexpr表示。

constexpr-representable在这里定义:https://eel.is/c++draft/expr.const#init-3,但在这里已经得到满足。

那么这个临时对象是否在潜在意义上可以在常量表达式中使用?
https://eel.is/c++draft/expr.const.init#8

一个对象或引用在点P 时可能在常量表达式中使用,若它满足下列条件:
(8.1) 它是由一个在点P 时可在常量表达式中使用的变量所声明的对象,
(8.2) 是一个常量限定的字面量类型的临时对象,其生命周期被扩展到可在常量表达式中使用的变量的生命周期;
(8.3) 是一个模板参数对象,
(8.4) 是一个字符串字面量对象,
(8.5) 上述对象的一个不可变子对象,或
(8.6) 上述对象的任意引用成员。

8.1:它不是引用
8.2:对于 T::n 是,因为它是一个 int const,其生命周期被扩展到与 T::n 相同的生命周期,但对于 T::p 不是,因为它是一个非-const int
8.3:它不是模板参数
8.4:它不是字符串字面量对象
8.5:它不是上述对象的一个不可变子对象
8.6:它不是上述对象的任意引用成员

因此通过对 T::p 进行左值到右值转换得到的右值不能在常量表达式中使用,gcc/clang拒绝该代码的判断是正确的。

此外还应注意,constexpr 实际上并非复现此问题所必需的:

template<int N>
struct s{
  static const int&& n; 
  static       int&& p;
};
template<int N> const int&& s<N>::n = N;
template<int N> int&& s<N>::p = N;

LIVE
它显示了相同的行为。

甚至我们还可以将示例简化为:

template<int N> const int&& nv = N;
template<int N> int&& pv = N;
static_assert(nv<1> == 1);
static_assert(pv<1> == 1);

LIVE

然而在这里EDG正确地拒绝了这段代码。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章