[PATCH] libstdc++: Fix construction function_ref from nontype<&S::x> and reference_wrapper [PR121858]
Tomasz Kaminski
tkaminsk@redhat.com
Wed Oct 29 18:22:55 GMT 2025
I just realized that unwrapping reference wrapper is observable, and not
permitted by standard,
for example this should pass
struct S { int x; };
S s1, s2;
std:::refernce_wrapper<S1> rw(s1);
std::function_ref<int&()> fr(std::nontype<&S::x>, rw);
assert( &fr() == &s1.x );
rw = std::ref(s2); // rebind
assert( &f1() == &s2.x ); // the rebind must be observed.
I will post a new patch, with the above fixed.
On Sat, Oct 25, 2025 at 7:01 AM Hewill Kang <hewillk@gmail.com> wrote:
> Thanks. The patch is LGTM.
>
> Tomasz Kaminski <tkaminsk@redhat.com> 於 2025年10月25日 週六 上午1:35寫道:
>
>>
>>
>> On Fri, Oct 24, 2025 at 7:03 PM Hewill Kang <hewillk@gmail.com> wrote:
>>
>>> `volatile std::reference_wrapper<S>` will cause `_Ur __uref = __ref;`
>>>>> hard error. But I'm not sure we really need to care about it.
>>>>>
>>>>>> result_of (where __inv_unwrap is sourced from), does not care about
>>>>>> it. So I would say, we do not need to.
>>>>>
>>>>>
>>>
>> Thanks for the explanation. Should I file a new bug regarding this? Only
>>> libstdc++ fails below:
>>>
>> You can, if you feel strongly about it. I presonally would not put that
>> as high priority issue, unless they are reports
>> of actual impact on non-synthetic examples.
>>
>>> #include <functional>
>>>
>>> int main() {
>>> struct S {
>>> void f() { };
>>> };
>>>
>>> S s;
>>> volatile auto sr = std::ref(s);
>>> static_assert(!std::is_invocable_v<decltype(&S::f), decltype(sr)>);
>>> }
>>>
>>> https://godbolt.org/z/b5oP75613
>>>
>>>
>>>
>>>
>>> Hewill Kang <hewillk@gmail.com> 於 2025年10月25日 週六 上午12:15寫道:
>>>
>>>> Here are some examples:
>>>>
>>>> struct S {
>>>> void f() const;
>>>> };
>>>>
>>>> int main() {
>>>> S s;
>>>> volatile auto sr = std::ref(s);
>>>> std::function_ref<void() const> fr(std::nontype<&S::f>, sr);
>>>> fr();
>>>> }
>>>>
>>>>
>>>> Hewill Kang <hewillk@gmail.com> 於 2025年10月25日 週六 上午12:10寫道:
>>>>
>>>>> `volatile std::reference_wrapper<S>` will cause `_Ur __uref = __ref;`
>>>>> hard error. But I'm not sure we really need to care about it.
>>>>>
>>>>> Tomasz Kamiński <tkaminsk@redhat.com> 於 2025年10月24日 週五 下午11:33寫道:
>>>>>
>>>>>> To reduce instantiation count, function_ref(nontype<&S::x>, r)
>>>>>> previously
>>>>>> reused the invoker from function_ref(nontype<&S::x>, &r). This
>>>>>> assumed r was
>>>>>> always a reference to S or a derived class. However, this constructor
>>>>>> is also
>>>>>> valid for lvalues (but not rvalues) of reference_wrapper
>>>>>> specializations.
>>>>>>
>>>>>> This patch fixes this by unwrapping the reference_wrapper<T> within
>>>>>> the
>>>>>> nontype<f> constructor when f is a member pointer. This is achieved
>>>>>> by casting
>>>>>> the argument to typename __inv_unwrap<_Td>::type _GLIBCXX_MOF_CV&.
>>>>>> This results
>>>>>> in U& if _Td is a (possibly-const) reference_wrapper<U>, and _Td
>>>>>> _GLIBCXX_MOF_CV&
>>>>>> otherwise. We use __inv_unwrap because unwrap_reference_t does not
>>>>>> handle
>>>>>> cv-qualified types.
>>>>>>
>>>>>> Consequently, a function_ref constructed from nontype<&S::x> and a
>>>>>> reference_wrapper (rw) now refers to rw.get() instead of the rw
>>>>>> object itself,
>>>>>> mitigating the risk of dangling references.
>>>>>>
>>>>>> Note that reference_wrapper arguments cannot be unwrapped in the
>>>>>> general case,
>>>>>> s the functor f referenced by nontype<f> may explicitly accept
>>>>>> reference_wrapper<T>&
>>>>>> as a parameter to observe identity or modify the argument.
>>>>>>
>>>>>> PR libstdc++/121858
>>>>>>
>>>>>> libstdc++-v3/ChangeLog:
>>>>>>
>>>>>> * include/bits/funcref_impl.h
>>>>>> (function_ref::function_ref(nontype<__fn>, _Up&&)): Unwrap
>>>>>> reference_wrapper.
>>>>>> * testsuite/20_util/function_ref/call.cc: Call and update
>>>>>> test05(). Add new test06() for reference_wrapper.
>>>>>> ---
>>>>>> Testing on x88_64-linux. OK for trunk?
>>>>>>
>>>>>> libstdc++-v3/include/bits/funcref_impl.h | 21 ++++--
>>>>>> .../testsuite/20_util/function_ref/call.cc | 75
>>>>>> ++++++++++++++-----
>>>>>> 2 files changed, 69 insertions(+), 27 deletions(-)
>>>>>>
>>>>>> diff --git a/libstdc++-v3/include/bits/funcref_impl.h
>>>>>> b/libstdc++-v3/include/bits/funcref_impl.h
>>>>>> index 44c992281be..cc510aba3a6 100644
>>>>>> --- a/libstdc++-v3/include/bits/funcref_impl.h
>>>>>> +++ b/libstdc++-v3/include/bits/funcref_impl.h
>>>>>> @@ -138,14 +138,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>>>>> if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
>>>>>> static_assert(__fn != nullptr);
>>>>>>
>>>>>> - using _Tr = _Td _GLIBCXX_MOF_CV&;
>>>>>> - if constexpr (is_member_pointer_v<_Fn> &&
>>>>>> is_lvalue_reference_v<_Tr>)
>>>>>> - // N.B. invoking member pointer on lvalue produces the
>>>>>> same effects,
>>>>>> - // as invoking it on pointer to that lvalue.
>>>>>> - _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td
>>>>>> _GLIBCXX_MOF_CV>;
>>>>>> + if constexpr (is_member_pointer_v<_Fn>)
>>>>>> + {
>>>>>> + // is_invocable_v with member pointer is only true for
>>>>>> reference
>>>>>> + // to class (or derived from it) or reference wrapper
>>>>>> to such,
>>>>>> + // we instead store pointer to referenced object
>>>>>> + using _Ur = typename __inv_unwrap<_Td>::type
>>>>>> _GLIBCXX_MOF_CV&;
>>>>>> + _Ur __uref = __ref;
>>>>>> + _M_invoke = &_Invoker::template _S_bind_ptr<__fn,
>>>>>> remove_reference_t<_Ur>>;
>>>>>> + _M_init(std::addressof(__uref));
>>>>>> + }
>>>>>> else
>>>>>> - _M_invoke = &_Invoker::template _S_bind_ref<__fn, _Tr>;
>>>>>> - _M_init(std::addressof(__ref));
>>>>>> + {
>>>>>> + _M_invoke = &_Invoker::template _S_bind_ref<__fn, _Td
>>>>>> _GLIBCXX_MOF_CV&>;
>>>>>> + _M_init(std::addressof(__ref));
>>>>>> + }
>>>>>> }
>>>>>>
>>>>>> /// Target object is equivalent to std::bind_front<_fn>(__ptr).
>>>>>> diff --git a/libstdc++-v3/testsuite/20_util/function_ref/call.cc
>>>>>> b/libstdc++-v3/testsuite/20_util/function_ref/call.cc
>>>>>> index 23253c33ee3..d986e94b7bf 100644
>>>>>> --- a/libstdc++-v3/testsuite/20_util/function_ref/call.cc
>>>>>> +++ b/libstdc++-v3/testsuite/20_util/function_ref/call.cc
>>>>>> @@ -130,7 +130,6 @@ int callback_ref(ftype& f, int x) { return f(x); }
>>>>>> void
>>>>>> test05()
>>>>>> {
>>>>>> -
>>>>>> function_ref<int(int)> r1(nontype<&callback_ptr>, &twice);
>>>>>> VERIFY( r1(2) == 4 );
>>>>>> function_ref<int(int)> r2(nontype<&callback_ptr>, cube);
>>>>>> @@ -140,27 +139,61 @@ test05()
>>>>>> VERIFY( r3(3) == 6 );
>>>>>> function_ref<int(int)> r4(nontype<&callback_ref>, cube);
>>>>>> VERIFY( r4(3) == 27 );
>>>>>> +}
>>>>>>
>>>>>> - // Checks if distinction between reference and pointer
>>>>>> - // is preserved.
>>>>>> - struct F
>>>>>> - {
>>>>>> - static
>>>>>> - int operator()(ftype* f, int x)
>>>>>> - { return f(x) + 1000; }
>>>>>> -
>>>>>> - static
>>>>>> - int operator()(ftype& f, int x)
>>>>>> - { return f(x) + 2000; }
>>>>>> +void
>>>>>> +test06()
>>>>>> +{
>>>>>> + struct S {
>>>>>> + int v;
>>>>>> + int& m() { return v; }
>>>>>> + const int& c() const { return v; }
>>>>>> };
>>>>>> - function_ref<int(int)> r5(nontype<F{}>, &twice);
>>>>>> - VERIFY( r5(2) == 1004 );
>>>>>> - function_ref<int(int)> r6(nontype<F{}>, twice);
>>>>>> - VERIFY( r6(2) == 2008 );
>>>>>> - function_ref<int(int)> r7(nontype<F{}>, &cube);
>>>>>> - VERIFY( r7(3) == 1006 );
>>>>>> - function_ref<int(int)> r8(nontype<F{}>, cube);
>>>>>> - VERIFY( r8(3) == 2027 );
>>>>>> + S s{10};
>>>>>> + std::reference_wrapper<S> sr(s);
>>>>>> + std::reference_wrapper<S> csr(s);
>>>>>> +
>>>>>> + std::function_ref<int&()> f1(std::nontype<&S::v>, sr);
>>>>>> + VERIFY( &f1() == &s.v );
>>>>>> + std::function_ref<const int&()> f2(std::nontype<&S::v>, sr);
>>>>>> + VERIFY( &f2() == &s.v );
>>>>>> + std::function_ref<int&()> f3(std::nontype<&S::m>, sr);
>>>>>> + VERIFY( &f3() == &s.v );
>>>>>> + std::function_ref<const int&()> f4(std::nontype<&S::c>, sr);
>>>>>> + VERIFY( &f4() == &s.v );
>>>>>> +
>>>>>> + std::function_ref<const int&()> f5(std::nontype<&S::v>, csr);
>>>>>> + VERIFY( &f5() == &s.v );
>>>>>> + std::function_ref<const int&()> f6(std::nontype<&S::c>, sr);
>>>>>> + VERIFY( &f6() == &s.v );
>>>>>> + static_assert( !std::is_constructible_v<
>>>>>> + std::function_ref<int&()>,
>>>>>> + std::nontype_t<&S::c>, std::reference_wrapper<S>&>
>>>>>> + );
>>>>>> +
>>>>>> + std::function_ref<int&()> f7(std::nontype<&S::v>,
>>>>>> std::as_const(sr));
>>>>>> + VERIFY( &f7() == &s.v );
>>>>>> + std::function_ref<const int&()> f8(std::nontype<&S::m>,
>>>>>> std::as_const(sr));
>>>>>> + VERIFY( &f8() == &s.v );
>>>>>> +
>>>>>> + // No rvalue reference wrapepr support
>>>>>> + static_assert( !std::is_constructible_v<
>>>>>> + std::function_ref<int&()>,
>>>>>> + std::nontype_t<&S::v>, std::reference_wrapper<S>>
>>>>>> + );
>>>>>> + static_assert( !std::is_constructible_v<
>>>>>> + std::function_ref<int&()>,
>>>>>> + std::nontype_t<&S::v>, std::reference_wrapper<const S>>
>>>>>> + );
>>>>>> +
>>>>>> + constexpr auto id = [](const std::reference_wrapper<S>& x)
>>>>>> + { return &x; };
>>>>>> +
>>>>>> + // identity of reference_wrapper is preserved
>>>>>> + std::function_ref<const std::reference_wrapper<S>*()>
>>>>>> f9(std::nontype<id>, sr);
>>>>>> + VERIFY( f9() == &sr );
>>>>>> + std::function_ref<const std::reference_wrapper<S>*()>
>>>>>> f10(std::nontype<id>, csr);
>>>>>> + VERIFY( f10() == &csr );
>>>>>> }
>>>>>>
>>>>>> struct Incomplete;
>>>>>> @@ -182,5 +215,7 @@ int main()
>>>>>> test02();
>>>>>> test03();
>>>>>> test04();
>>>>>> + test05();
>>>>>> + test06();
>>>>>> test_params();
>>>>>> }
>>>>>> --
>>>>>> 2.51.0
>>>>>>
>>>>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20251029/d548ca08/attachment-0001.htm>
More information about the Libstdc++
mailing list