c++ 什么时候在常量表达式中允许将引用绑定到临时对象?

hsgswve4  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(147)

根据C20标准,下面哪个例子是法律的C

inline constexpr auto const& v = bool{}; // #1 - all ok
static_assert([]{
    constexpr auto const& x = bool{}; // #2 - clang nope, gcc nope, msvc ok
    return not x;
}());
struct s { constexpr s(auto const&) {} };
static_assert([]<s = bool{}>{ return true; }()); // #3 - clang nope, gcc ok, msvc ok

Live example
来自Clang的错误消息,示例为#2

<source>:5:27: error: constexpr variable 'x' must be initialized by a constant
expression
    5 |     constexpr auto const& x = bool{};
      |                           ^   ~~~~~~
<source>:5:27: note: reference to temporary is not a constant expression
<source>:5:31: note: temporary created here
    5 |     constexpr auto const& x = bool{};
      |

来自GCC的错误消息,示例为#2

<source>:5:36: error: '<anonymous>' is not a constant expression
    5 |     constexpr auto const& x = bool{};
      |                                    ^
<source>: At global scope:
<source>:7:2: error: non-constant condition for static assertion
    4 | static_assert([]{
      |               ~~~
    5 |     constexpr auto const& x = bool{};
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    6 |     return not x;
      |     ~~~~~~~~~~~~~
    7 | }());
      | ~^~
<source>:7:2: error: '<lambda()>' called in a constant expression
<source>:4:15: note: '<lambda()>' is not usable as a 'constexpr' function because:
    4 | static_assert([]{
      |               ^

来自Clang的错误消息,示例为#3

<source>:10:15: error: no matching function for call to object of type
'(lambda at <source>:10:15)'
   10 | static_assert([]<s = bool{}>{ return true; }());
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:10:15: note: candidate template ignored: substitution failure: conversion
from 'bool' to 's' in converted constant expression would bind reference to a
temporary
   10 | static_assert([]<s = bool{}>{ return true; }());
      |               ^      ~~~~
mec1mxoz

mec1mxoz1#

这里的规则是constexpr引用 * 必须 * 引用一个具有静态存储持续时间的对象(这是现在[expr.const]/13中的“允许结果”规则)。也就是说,constexpr引用必须有一个作为常量表达式的地址。这是很多人感到惊讶的事情,因为这是病态的:

void f() {
    constexpr int a = 1;    // ok
    constexpr auto* p = &a; // error
}

因为p需要指向具有恒定地址的对象,而每次调用f时,a都具有不同的地址。所以这行不通。
这条规则解释了你的例子中发生了什么。这是因为inline

inline constexpr auto const& v = bool{}; // #1 - all ok

临时对象的生存期被延长为v的生存期,v具有静态存储持续时间。这是一个固定地址。
但这是不好的,因为同样的原因,我的简短的例子是不好的(MSVC在技术上是错误的接受这一点)-x在这里只是一个局部变量,它所引用的对象没有常量地址。

static_assert([]{
    constexpr auto const& x = bool{}; // #2 - clang nope, gcc nope, msvc ok
    return not x;
}());

[2]至少目前的规则是这样说的。这是一个相当令人困惑的规则,并阻止了许多看起来合理的代码工作。最突出的这种看起来合理的代码是结构化绑定:

void f() {
    constexpr auto t1 = std::tuple(1)
    static_assert(std::get<0>(t1) == 0); // ok

    constexpr auto [t2] = std::tuple(2); // error
}

这是因为t2的声明是对局部变量的constexpr引用。
P2686中,当前的方向是所谓的“符号寻址”。也就是说,我们并没有尝试用一个常量地址来初始化p(这不起作用),而是将其定义为“变量a的地址“(依赖于它总是a的地址,即使该地址不是常量)。您仍然不能将p用作非类型模板参数(我认为大多数人对此不会感到惊讶),但它将允许大量合理的代码。报纸上有更多的细节。

相关问题