temporary lifetime in range-for expression

后端 未结 2 1780
死守一世寂寞
死守一世寂寞 2020-12-14 18:19

Consider a simple class A that can be used as a range:

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

    const char* begin() const {
            


        
相关标签:
2条回答
  • 2020-12-14 18:45

    The reason the lifetime of the temporary is not extended is how the standard defines range-based for loops in

    6.5.4 The range-based for statement [stmt.ranged]

    1 For a range-based for statement of the form

    for (for-range-declaration:expression)statement

    let range-init be equivalent to the expression surrounded by parentheses

    ( expression )

    and for a range-based for statement of the form

    for (for-range-declaration:braced-init-list)statement

    let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to

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

    Note that auto && __range = range-init; would extend the lifetime of a temporary returned from range-init, but it does not extend the lifetime of nested temporaries inside of range-init.

    This is IMHO a very unfortunate definition and was even discussed as Defect Report 900. It seems to be the only part of the standard where a reference is implicitly bound to extend the lifetime of an expressions result without extending the lifetime of nested temporaries.

    The solution is to store a copy in the wrapper - which often defeats the purpose of the wrapper.

    0 讨论(0)
  • 2020-12-14 18:53

    Lifetime extension only occurs when binding directly to references outside of a constructor.

    Reference lifetime extension within a constructor would be technically challenging for compilers to implement.

    If you want reference lifetime extension, you will be forced to make a copy of it. The usual way is:

    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;
    };
    

    In many contexts, wrap is itself a template:

    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;
    };
    

    and if A is a Foo& or a Foo const&, references are stored. If it is a Foo, then a copy is made.

    An example of such a pattern in use would be if wrap where called backwards, and it returned iterators that where reverse iterators constructed from A. Then temporary ranges would be copied into backwards, while non-temporary objects would be just viewed.

    In theory, a language that allowed you to markup parameters to functions and constructors are "dependent sources" whose lifetime should be extended as long as the object/return value would be interesting. This probably is tricky. As an example, imagine new wrap( A{"works"} ) -- the automatic storage temporary now has to last as long as the free store wrap!

    0 讨论(0)
提交回复
热议问题