以下代码():
#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
中给出的示例相同。
因为取消引用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嗯,但这不是你在答案中给出的解释。:)
–
|
-O3
问题。–
–
–
[] ( const S& l ) { return l.x; }
返回一个值,这是安全的。并std::views::transform( &S::x )
使用临时的引用,与 相同[] ( const S& l ) -> const float& { return l.x; }
,这是 UB:–
S
在访问和复制其字段的引用之前临时销毁float t :
–
|