匿名结构中的常量引用成员变量是否会延长临时变量的生命周期?

Gug*_*ugi 20 c++ lifetime language-lawyer c++14

考虑以下代码:

struct Temp{ int i = 0; };

Temp GetTemp() { return Temp{}; }

int main()
{
    for (struct{Temp const & t; int counter;} v = {GetTemp(), 0};
        v.counter < 10; 
        ++v.counter) 
    {
      // Is v.t guaranteed to not be a dangling reference?
      std::cout << v.t.i << std::endl;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此GetTemp()返回一个临时对象,然后将其分配给常量引用变量。但是,该常量引用变量是匿名本地结构的成员。问题:C++ 标准是否保证该临时对象的生命周期延长到循环终止之后?

考虑到这个问题,我预计答案是否定的,即我在循环体中得到了一个悬空引用。然而, gcc 和 clang 似乎延长了生命周期(参见godbolt 上的示例),甚至不抱怨-fsanitize=undefined,这让我感到惊讶。

Col*_*mbo 12

对于示例中的支撑聚合初始化,自 C++98 起就保证了生命周期扩展(无论类的链接/可见性属性如何)。这是直观的,因为引用直接绑定到临时变量,而不是通过一些中间 ctor 参数,如您链接的问题中所示。有关法律术语,请参阅C++14 FD 中的[class.temporary] ,其中概述了生命周期扩展上下文。

另请参阅此处的注释,该注释区分自 C++20 以来的花括号初始化和括号初始化:

[注7:与direct-list-initialization相比,允许缩小转换 ([dcl.init.list]),不允许指示符,绑定到引用的临时对象不会延长其生命周期 ([class.temporary ]),并且没有大括号省略。
[示例3:

struct A {
    int a;
    int&& r;
};

int f();
int n = 10;

A a1{1, f()};                   // OK, lifetime is extended 
A a2(1, f());                   // well-formed, but dangling reference
Run Code Online (Sandbox Code Playgroud)


Ted*_*gmo 5

匿名结构中的常量引用成员变量是否会延长临时变量的生命周期?

是的,参考链const是完整的。只有 isvv一直存活到循环结束for,因此引用的生命周期Temp会延长到那时。匿名这一事实struct没有任何影响。

class.temporary/4

在两种情况下,临时变量会在与完整表达式结束时不同的时间点被销毁。第一个上下文是调用默认构造函数来初始化数组的元素时。如果构造函数有一个或多个默认参数,则在构造下一个数组元素(如果有)之前,将按顺序销毁在默认参数中创建的每个临时参数。

class.temporary/5

第二个上下文是当引用绑定到临时对象时。引用绑定到的临时对象或引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,但以下情况除外:

  • (5.1) 构造函数构造函数初始化程序 ([class.base.init]) 中引用成员的临时绑定将持续存在,直到构造函数退出。
  • (5.2) 函数调用 ([expr.call]) 中对引用参数的临时绑定将持续存在,直到包含该调用的完整表达式完成为止。
  • (5.3) 绑定到函数返回语句 ([stmt.return]) 中返回值的临时变量的生命周期不会延长;临时值在 return 语句中完整表达式的末尾被销毁。
  • (5.4) 临时绑定到 new-initializer ([expr.new]) 中的引用将持续存在,直到包含 new-initializer 的完整表达式完成为止。

生命周期延长的任何例外情况均不适用于您的情况。