Passing a reference-to-function as a universal reference
Asked Answered
L

2

7

I'm struggling to understand what exactly happens when passing a reference-to-function to a function as a universal reference (what type is being deduced). Let's suppose we have a function foo that takes a param as a universal reference:

template<typename T>
void foo(T&& param)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

And then let's do the following:

void(&f)(int) = someFunction;
foo(f);

The result will be:

void foo(T&&) [with T = void (&)int]

This is perfectly understandable: we are passing lvalue to our function foo, so the deduced type is void(&)int, and the type of the param will be "void(&& &)int" which under reference collapsing rules becomes void(&)int. Param will be just an lvalue reference to a function.

But when I do the following:

void(&f)(int) = someFunction;
foo(std::move(f));

foo will print:

void foo(T&&) [with T = void (&)int]

which is exactly the same as before! What is happening here? Why the result is the same as when passing lvalue? I would expect that since we are passing rvalue to foo, the deduced type should be T = void(int), and param should become void(&&)int. This always happen with all other "normal" types (like classes, primitive types, etc.) Why is it different when dealing with function references?

Leavetaking answered 29/6, 2015 at 20:58 Comment(3)
Nah. param has a name, it's an lvalue by definition.Drice
@KerrekSB Sorry but that's wrong - #7017277Leavetaking
@rubix_addict: My bad, thanks! Removed. I was after the statement that's part of the answer, but I didn't get it right.Median
P
8

A std::move is a glorified static_cast to rvalue reference type. The standard says that casting to an rvalue reference to function type still yields an lvalue. Per [expr.static.cast]/p1:

The result of the expression static_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue;

Concerning the value category of the std::move() function call, which returns an rvalue reference designating the result of the conversion, we can also see from [expr.call]/p10 that a function call is an lvalue if its return type is an rvalue reference to function type:

A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.

Prajna answered 29/6, 2015 at 21:25 Comment(2)
Very nice answer. It makes intuitive sense that reference to function type would be treated a bit differently; functions don't have state and therefore cannot be moved from, and a reference to a function can never change the function. reference to function types effectively always behave as const &. Although it does still leave me wondering why it was necessary to explicitly state that these conversions occur. What weird, senseless code examples could be concocted in the absence of this rule?Gossip
Ah, nice! It seems a bit complicated when there are multiple ways of obtaining what would be a function xvalue, to say for each way that it actually results in a function lvalue instead, but it is what it is. :)Haitian
T
0

Rvalue references to functions are always considered to be lvalue references, whether named or not; hence universal references deduce rvalue references to functions to be lvalue references to functions.

The rvalue vs lvalue distinction makes no sense for functions, since functions do not have a lifetime.

Turves answered 29/6, 2015 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.