使用对局部变量的引用来初始化 constexpr 变量是否有效?

Is it valid to use a reference to a local variable to initialize a constexpr variable?

提问人:phinz 提问时间:10/31/2023 最后编辑:phinz 更新时间:11/4/2023 访问量:305

问:

以下代码仅在 GCC 上编译(在 godbolt.org 上的 10.4 和 13.2 上检查了它),但不能在 Clang 上编译(在我尝试过的所有版本上都失败,例如 godbolt.org 上的 17.0.1):

struct A {
  static constexpr int b{1};
};

int main(int argc, char *argv[]) { 
    A a;
    A& aref{a};
    constexpr auto bb1{a.b};
    constexpr auto bb2{aref.b};
    return bb1+bb2; 
}

Clang 输出:

<source>:9:20: error: constexpr variable 'bb2' must be initialized by a constant expression
    9 |     constexpr auto bb2{aref.b};
      |                    ^  ~~~~~~~~
<source>:9:24: note: initializer of 'aref' is not a constant expression
    9 |     constexpr auto bb2{aref.b};
      |                        ^
<source>:7:14: note: declared here
    7 |     A& aref{a};
      |   

https://godbolt.org/z/nG4j3KefE

为什么?

C++ Clang 语言-Lawyer constexpr

评论

1赞 cigien 10/31/2023
Clang 是正确的。基本上是 stackoverflow.com/questions/60454862 的复制品
0赞 Evg 10/31/2023
可能,相关:stackoverflow.com/questions/54124899/......
3赞 Marek R 10/31/2023
上面两个建议的链接都是指函数参数。这里我们没有函数,而是对变量和静态类字段的引用。所以问题是不同的。另请注意,这是一个由 (dot) 引用的静态字段。A::b.
1赞 cigien 10/31/2023
@phinz我很确定这是一个 msvc 错误。
1赞 cigien 10/31/2023
实际上,再次阅读措辞,关于引用的规则似乎在 C++23 中发生了变化:例如,我们现在可以在 constexpr 上下文中使用引用参数(仍然必须遵循其他规则,但仅仅它是引用这一事实并不能自动阻止持续计算)。所以也许这段代码现在应该有效了。

答:

-4赞 Lozminda 10/31/2023 #1

我觉得这是一个与这个问题这个问题相似的“物种”问题。最后一个答案(这个问题)有一个评论,即 gcc 无法识别错误,这是贯穿上述答案的主题。 你的问题的答案可能只是一个平淡无奇的“clang比gcc更接近C++标准”。我相信每个人都知道编写编译器绝非易事,有些语法只是逃脱了 tokeniser/开发团队等。再加上完成软件的下行压力,GCC 团队无法涵盖当前 C++ 标准以及 LLVM 团队的所有方面。

引用这篇文章(接近结尾):“Clang 和 LLVM 比 GCC 更严格地遵守 C 和 C++ 标准。在 GCC 升级期间不会发生 GNU 内联和其他问题。

这是一个不那么平淡无奇的答案

如果:

struct A {
  static constexpr int b{1};
};

A a;   // a is now global 

int main(int argc, char *argv[]) { 
    // A a;
    A& aref{a};
    constexpr auto bb1{a.b};
    constexpr auto bb2{aref.b};
    return bb1+bb2; 
}

(如果)A a 是全局的,即在 main( 之前声明,所有编译器都会编译,因为引用不会是本地对象,因此可以是 const(对于 constexpr)。对本地对象的引用(即使在 main() 中)也不被视为 const。

评论

0赞 user207421 11/4/2023
这不是语法问题,您显然不知道编译器开发过程中发生了什么。
0赞 Lozminda 11/4/2023
为什么 LLVM 和 GCC 都会生成(例如)已知的错误报告,缺陷报告 (P2280) 不是突出了语法问题,或者更具体地说是它的解释吗?
6赞 user17732522 11/1/2023 #2

bb1总是被允许的。

bb2自 P2280 以来是允许的,P2280 也被 C++23 接受为早期修订版的缺陷报告。

最初有一个特定的规则,即在常量表达式中禁止使用任何在常量表达式中不可用的引用变量(但只能使用引用变量!)(即 使用常量表达式初始化)或在常量表达式计算期间开始其生存期。即使没有应用左值到右值的转换,并且没有其他任何内容依赖于特定的引用对象,这也成立。这与非参考变量的使用不一致,缺陷报告修复了这个问题。您的示例演示了不一致之处。constexpr

从技术上讲,GCC 在缺陷报告之前没有表现出符合性,而 Clang 在缺陷报告之后也没有表现出符合性,但可能只是还没有实现它。

评论

0赞 phinz 11/1/2023
您能告诉我原始规则背后的原因是什么吗?只是设计错误还是其他原因?特别是在这种情况下,禁止这样做似乎很愚蠢,我使用引用作为别名来缩短嵌套访问。
2赞 user17732522 11/1/2023
@phinz 它自 C++11 以来一直存在,可能很难找到理由,但我认为当时的重点是允许非常简单的按值函数取代模板元编程中的宏和算术。我想没有把重点放在通过引用使用对象上,正如您在 P2280 中看到的那样,在放宽规则时仍然需要考虑一些细节。constexpr
0赞 phinz 11/1/2023
谢谢,我有一个模糊的想法,它可以做一些事情来避免多态性混淆,但这更有意义。
2赞 user17732522 11/1/2023
@phinz 为了支持我的猜想,他们只允许在上下文中引用 open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3078.html 在起草C++11时相对较晚。在此之前,只允许使用值参数(无论如何,C++11 中不允许在函数中使用变量声明)。constexprconstexpr