On Thu, 27 Mar 2025 at 08:38, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > On Wed, Mar 26, 2025 at 12:29 PM Jonathan Wakely <jwak...@redhat.com> wrote: >> >> The result of std::move (or a cast to an rvalue reference) on a function >> reference is always an lvalue. Because std::ranges::iter_move was using >> the type std::remove_reference_t<X>&& as the result of std::move, it was >> giving the wrong type for function references. Use a decltype-specifier >> with declval<remove_reference_t<X>>() instead of just using the >> remove_reference_t<X>&& type directly. This gives the right result, >> while still avoiding the cost of doing overload resolution for >> std::move. >> >> libstdc++-v3/ChangeLog: >> >> PR libstdc++/119469 >> * include/bits/iterator_concepts.h (_IterMove::__result): Use >> decltype-specifier instead of an explicit type. >> * testsuite/24_iterators/customization_points/iter_move.cc: >> Check results for function references. >> --- >> >> This one is weird, but I think the fix is right. > > Interesting, for T being a function type, T&& is forming rvalue reference to > function type, > but we only yield values of function type, so decltype would give T&.
Yes, it's a very strange corner of the language. [basic.lval] p4 is a note which includes "rvalue references to functions are treated as lvalues whether named or not". [expr.static.cast] and [expr.reinterpret.cast] and [expr.type.conv] all have wording like "the result is an lvalue if T is an lvalue reference type or an rvalue reference to function type and an xvalue otherwise." And the final piece of the puzzle is that [dcl.init.ref] (5.3.1) allows rvalue references to bind to lvalues of function type, which is what allows std::move to compile, even though the return type is an rvalue reference and the return statement produces an lvalue. So although the return type of std::move(*funref) is remove_reference_t<decltype(funref)>&&, the actual result of std::move(*funref) is not that type! Using decltype(std::move(*funref)) gets the core language "it's always an lvalue even when it's not" behaviour that we want. An alternative would be something like: conditional_t<is_function_v<remove_reference_t<T>>, T, remove_reference_t<T>&&> but it seems simpler and cheaper to just use decltype(declval<remove_reference_t<T>>()), as this is equivalent to the decltype(std::move(*funref)) type. > LGTM. > I am not sold on why I would care about the result of calling iter_move on > function reference > (not iterator returning function reference), but standard seem to require > that we support it, Yes, it seems completely useless, but because I was fixing ranges::iter_move recently I decided to deal with this right away. > because * works on them due decay to pointer. Yes, that's the other weird part of this. *funref decays the function reference to a function pointer, which is then dereferenced to get the original function reference back. So std::move(funref) and std::move(*funref) are identical.