范围内的临时生命周期表达式

Bar*_*rry 18 c++ c++11

考虑一个A可以用作范围的简单类:

struct A { 
    ~A() { std::cout << "~A "; }

    const char* begin() const {
        std::cout << "A::begin ";
        return s.data();
    }   

    const char* end() const {
        std::cout << "A::end ";
        return s.data() + s.size();
    }   

    std::string s;
};
Run Code Online (Sandbox Code Playgroud)

如果我A在范围内制作一个临时的,它的工作原理与我希望的完全一样:

for (auto c : A{"works"}) {
    std::cout << c << ' ';
} 

// output
A::begin A::end w o r k s ~A 
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试包装临时:

struct wrap {
    wrap(A&& a) : a(std::move(a))
    { } 

    const char* begin() const { return a.begin(); }
    const char* end() const { return a.end(); }

    A&& a;
};

for (auto c : wrap(A{"fails"})) {
    std::cout << c << ' ';
}

// The temporary A gets destroyed before the loop even begins: 
~A A::begin A::end 
^^
Run Code Online (Sandbox Code Playgroud)

为什么A不能延长整个范围的生命周期表达,如何在不诉诸副本的情况下实现这一目标A

Yak*_*ont 8

仅当直接绑定到构造函数外部的引用时,才会发生生命周期扩展.

构造函数中的引用生命周期扩展对于编译器来说在技术上具有挑战性.

如果您想要参考生命周期扩展,您将被迫复制它.通常的方法是:

struct wrap {
  wrap(A&& a) : a(std::move(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};
Run Code Online (Sandbox Code Playgroud)

在许多情况下,wrap它本身就是一个模板:

template<class A>
struct wrap {
  wrap(A&& a) : a(std::forward<A>(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};
Run Code Online (Sandbox Code Playgroud)

如果A是a Foo&或a Foo const&,则存储引用.如果是a Foo,则复制.

使用这种模式的一个例子是wrap在调用的地方backwards,它返回迭代器构造的反向迭代器A.然后将临时范围复制到backwards,而非临时对象将被查看.

从理论上讲,允许您将参数标记为函数和构造函数的语言是"依赖源",只要对象/返回值有趣,其生命周期就应该延长.这可能很棘手.作为一个例子,想象一下new wrap( A{"works"} )- 自动存储临时现在必须持续与免费商店一样长wrap!


Dan*_*rey 7

不延长临时生命周期的原因是标准如何定义基于范围的for循环

6.5.4基于范围的语句[stmt.ranged]

1对于for表单的基于范围的声明

for (for-range-declaration :表达式)语句

range-init等同于括号括起来的表达式

( expression )

以及基于范围for的表单声明

for (for-range-declaration :braced-init-list )语句

range-init等同于braced-init-list.在每种情况下,基于范围的for陈述等同于

{
   auto && __range = range-init;
   for ( auto __begin = begin-expr,
              __end = end-expr;
         __begin != __end;
         ++__begin ) {
      for-range-declaration = *__begin;
      statement
   }
}
Run Code Online (Sandbox Code Playgroud)

请注意,auto && __range = range-init;会延长临时从返回的寿命范围,初始化,但它并没有嵌套的临时的寿命延长范围,初始化.

这是恕我直言,这是一个非常不幸的定义,甚至被讨论为缺陷报告900.它似乎是标准的唯一部分,其中引用被隐式绑定以延长表达式结果的生命周期而不延长嵌套临时值的生命周期.

解决方案是在包装器中存储一个副本 - 这经常会破坏包装器的用途.