This question is a follow-up question to: Second overload of std::foward (example on cppreference.com).
StoryTeller's answer made me think about the value categories involved in the statement foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
. Doesn't the second overload
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
potentially cause dangling references? Changing my example of the linked question to
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
// now according to the 2nd overload of std::forward
template <typename T>
T&& myForward(typename std::remove_reference_t<T>&& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
// now with the inner and the outer forward as in the example on cppreference
template <typename T>
void wrapper(T&& t)
{
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
}
int main()
{
wrapper(foo());
return 0;
}
made me think that there is already a dangling reference involved: myForward<T>(t).get()
returns an int
prvalue whose evaluation initializes a temporary object that typename std::remove_reference_t<T>&& t
binds to when calling myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get())
. (According to cppreference, this conversion "[...] produces an xvalue denoting the temporary object." But what exactly is the xvalue expression when triggering a temporary materialization conversion by binding a prvalue to a reference? Is it even existent in the code? It's not myForward<T>(t).get()
because that's a prvalue. Or is this prvalue converted to an xvalue by binding it to a reference in myForard
?)
Next, myFoward
returns an rvalue-reference int&&
to this variable local to myForward
, which the int&&
of the func()
-overload then binds to. Initially, I'd thought this already introduces a dangling reference, but then I read the paragraph "Lifetime of a temporary" of the article "Reference initialization" on cppreference.com. There are "exceptions to this lifetime rule":
"a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference."
In conclusion, this temporary object local to myForward exists up until the semicolon of the full expression — all good. However, my interpretation of "if the function returns a reference, which outlives the full expression, it becomes a dangling reference" is "if the function returns a reference to that reference parameter". That should mean that changing wrapper(T&& t)
like so
template <typename T>
void wrapper(T&& t)
{
// func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
int&& ref = std::forward<decltype(std::forward<T>(t).get())>(std::forward<T>(t).get());
func(ref);
}
should result in ref being a dangling reference. Still, I can print "42" (the correct value) in void func(int&& rvalue)
, even when putting some other values on the stack between binding to int&& ref
and calling func(ref)
. Also, printing the address of t
in myForward
, ref
in wrapper
and rvalue
in func
always gives the same value, which means it's the same object (local to myForward
).
At the same time, that should mean that one is dealing with a dangling reference when calling the second overload of std::forward
with a prvalue and not using the reference returned by it in the same expression. Or am I missing something regarding this topic?
Aucun commentaire:
Enregistrer un commentaire