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.

Reply via email to