以下代码():

#include <iostream>
#include <vector>
#include <ranges>


struct S
{
    float x = 150.f;
    S f() const
    {
        return *this;
    }
};


int main()
{
    std::vector<S> vec{
        { 1.f },
        { 2.f }
    };

    std::cout << "\nCreated\n";

    for ( const auto& l : vec )
    {
        std::cout << l.f().x << ' ';
    }
    std::cout << "\nView0\n";
    
    for ( float t : vec
                    | std::views::transform( &S::f )
                    | std::views::transform( &S::x )
        )
    {
        std::cout << t << ' ';
    }
    std::cout << "\nView1\n";

    auto view1
        = vec
        | std::views::transform( &S::f )
        | std::views::transform( [] ( const S& l ) { return l.x; } );

    for ( float t : view1 )
    {
        std::cout << t << ' ';
    }
}

产生以下输出(对于启用了优化的 Clang 和 GCC):

Created
1 2 
View0
0 0 
View1
1 2

我发现零不是固定的,得到的输出View0看起来像未定义的行为,但是我不知道为什么。

另一个观察结果是,-Wall -Wextra -pedantic-errors在 GCC 中启用会导致出现一些警告:

<source>:33:52: warning: using a dangling pointer to an unnamed temporary [-Wdangling-pointer=]
   33 |                     | std::views::transform( &S::x )
      |                                                    ^
In file included from <source>:3:
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/ranges:1949:54: note: unnamed temporary defined here
 1949 |           { return std::__invoke(*_M_parent->_M_fun, *_M_current); }
<source>:33:52: warning: '<unnamed>.S::x' may be used uninitialized [-Wmaybe-uninitialized]
   33 |                     | std::views::transform( &S::x )
      |                                                    ^
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/ranges:1949:54: note: '<anonymous>' declared here
 1949 |           { return std::__invoke(*_M_parent->_M_fun, *_M_current); }

我的问题是:为什么之后的输出View0不等于Created和之后的输出View1

10

  • 2
    没有的话-O3问题


    – 

  • 较小的 MRE:


    – 

  • 3
    @康桓瑋就好。


    – 


  • 1
    [] ( const S& l ) { return l.x; }返回一个值,这是安全的。并std::views::transform( &S::x )使用临时的引用,与 相同[] ( const S& l ) -> const float& { return l.x; },这是 UB:


    – 

  • 1
    @WeijunZhou,S在访问和复制其字段的引用之前临时销毁float t :


    – 


最佳答案
2

中给出的示例相同

因为取消引用views::transform(&S::f)会产生一个纯右值,views::transform(&S::x)所以会产生一个对物化临时对象的引用,该引用一旦operator*返回就会变成悬空,就像原始问题中描述的那样。

S::f()从其签名中返回一个临时变量;S f() const;。引用此临时变量的成员变量是未定义的行为

另一方面,获取临时变量的成员变量的值是明确定义的行为,这就是为什么这是完全没问题的:

std::views::transform( [] ( const S& l ) { return l.x; } );

lambda 按值返回。直到 C++23,临时范围表达式都是 UB

简单的解决方法是将签名更改为通过左值引用返回;const S& f() const;。然后其他一切都将正常工作。

11

  • 为什么服用const T&更安全?这会延长临时人员的寿命吗?


    – 

  • 1
    嗯?形成指向临时变量的指针会导致编译错误,而不是 UB。而 OP 并没有这样做,他们将成员指针应用于临时变量,这本身是合法的。


    – 


  • @Fedor 请参阅


    – 


  • 1
    您可以将(const S& l)lambda 更改为(S l),并且清理器不会发出任何抱怨。


    – 

  • 3
    嗯,但这不是你在答案中给出的解释。:)


    –