The following code (godbolt):
#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 << ' ';
}
}
produces the following output (for Clang and GCC with optimizations enabled):
Created
1 2
View0
0 0
View1
1 2
I've found that the zeros are not fixed, getting the output of View0
looks like undefined behavior, however I fail to see why.
Another observation is that enabling -Wall -Wextra -pedantic-errors
in GCC causes some warnings to appear:
<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); }
My question is: Why is the output after View0
not equal to the output after Created
and View1
?
Your example is essentially equivalent to the example given in LWG 3502.
Since dereferencing views::transform(&S::f)
yields a prvalue, views::transform(&S::x)
will produce a reference into the materialized temporary that becomes dangling as soon as operator*
returns, as described in the original issue.