mirror of
https://gcc.gnu.org/git/gcc.git
synced 2026-02-22 12:00:03 -05:00
This patch makes the function_ref non-dangling for the stateless
wrappers:
* any functor for which operator() selected for arguments is static,
* standard functors, including pre-C++20 ones.
In other words, any function_ref fr, that is constructed from stateless
wrapper w, can be still called after the object w is destroyed, e.g.:
std::function_ref<bool(int, int)> fr(std::ranges::less{});
fr(1, 2); // OK, previously UB because fr referred to already destroyed
// temporary
As function_ref's operator() is not constexpr, we test the change by checking
if the above declaration can be made constexpr, as such variable cannot contain
dangling pointer values.
We adjust the function_ref generic constructor from any functor, to use more
specialized invoker:
* _S_static (newly added) if the called operator() overload is static,
after changes r16-5624-g0ea9d760fbf44c, this covers all post-c++20 functors;
* _S_nttp<_Fd{}> for pre-C++20 standard functors.
In both above cases the value of _M_ptrs is ignored and simply set to nullptr.
This follows same technique (checking _Fd::operator()(args...)), and support
the same set of types, as for one used for the transform views iterators in
r16-5625-g9ed821d107f7a1.
As after this change we provide well-defined behavior for the code, that
previous was undefined, this changes is pure quality-of-implementation.
As illustrated by the test cases, it has observable side effects, where
non-longer dangling constructs can be used to define constexpr function_ref.
However, the standard does not define when the constructors defined constexpr
are actually usable at compile time, and the already have precedent in form
of SSO string for validity such constructs being implementation specific.
libstdc++-v3/ChangeLog:
* include/bits/funcref_impl.h (function_ref::function_ref(_Fn&&)):
Use _S_static and _S_nttp invokers.
* include/bits/funcwrap.h (_Base_invoker::_S_static):
Define.
* include/bits/stl_function.h (std::__is_std_op_template)
(std::__is_std_op_wrapper) [__cplusplus > 201703L]:
Moved from std/ranges.
* include/std/ranges (__detail::__is_std_op_template)
(__detail::__is_std_op_wrapper): Moved to bits/stl_function.h.
* testsuite/20_util/function_ref/dangling.cc: New test.
* testsuite/20_util/function_ref/dangling_neg.cc: New test.
Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
219 lines
7.6 KiB
C++
219 lines
7.6 KiB
C++
// Implementation of std::function_ref -*- C++ -*-
|
|
|
|
// Copyright The GNU Toolchain Authors.
|
|
//
|
|
// This file is part of the GNU ISO C++ Library. This library is free
|
|
// software; you can redistribute it and/or modify it under the
|
|
// terms of the GNU General Public License as published by the
|
|
// Free Software Foundation; either version 3, or (at your option)
|
|
// any later version.
|
|
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// Under Section 7 of GPL version 3, you are granted additional
|
|
// permissions described in the GCC Runtime Library Exception, version
|
|
// 3.1, as published by the Free Software Foundation.
|
|
|
|
// You should have received a copy of the GNU General Public License and
|
|
// a copy of the GCC Runtime Library Exception along with this program;
|
|
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
/** @file include/bits/funcref_impl.h
|
|
* This is an internal header file, included by other library headers.
|
|
* Do not attempt to use it directly. @headername{functional}
|
|
*/
|
|
|
|
#ifndef _GLIBCXX_MOF_CV
|
|
# define _GLIBCXX_MOF_CV
|
|
#endif
|
|
|
|
namespace std _GLIBCXX_VISIBILITY(default)
|
|
{
|
|
_GLIBCXX_BEGIN_NAMESPACE_VERSION
|
|
|
|
/// @cond undocumented
|
|
namespace __polyfunc
|
|
{
|
|
template<bool _Noex, typename _Ret, typename _Class, typename... _Args>
|
|
struct __skip_first_arg<_Ret(_Class::*)(_Args...) _GLIBCXX_MOF_CV
|
|
noexcept(_Noex)>
|
|
{ using type = _Ret(_Args...) noexcept(_Noex); };
|
|
|
|
template<bool _Noex, typename _Ret, typename _Class, typename... _Args>
|
|
struct __skip_first_arg<_Ret(_Class::*)(_Args...) _GLIBCXX_MOF_CV&
|
|
noexcept(_Noex)>
|
|
{ using type = _Ret(_Args...) noexcept(_Noex); };
|
|
} // namespace __polyfunc
|
|
/// @endcond
|
|
|
|
/**
|
|
* @brief Non-owning polymorphic function wrapper.
|
|
* @ingroup functors
|
|
* @since C++26
|
|
* @headerfile functional
|
|
*
|
|
* The `std::function_ref` class template is a non-owning call wrapper,
|
|
* that refers to a bound object. Using function_ref outside of the lifetime
|
|
* of the bound object has undefined behavior.
|
|
*
|
|
* It supports const-qualification and no-throw guarantees. The
|
|
* qualifications and exception-specification of the signature are respected
|
|
* when invoking the reference function.
|
|
*/
|
|
template<typename _Res, typename... _ArgTypes, bool _Noex>
|
|
class function_ref<_Res(_ArgTypes...) _GLIBCXX_MOF_CV
|
|
noexcept(_Noex)>
|
|
{
|
|
static_assert(
|
|
(std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && ...),
|
|
"each parameter type must be a complete class");
|
|
|
|
using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>;
|
|
using _Signature = _Invoker::_Signature;
|
|
|
|
// [func.wrap.ref.ctor]/1 is-invokable-using
|
|
template<typename... _Tps>
|
|
static constexpr bool __is_invocable_using
|
|
= __conditional_t<_Noex,
|
|
is_nothrow_invocable_r<_Res, _Tps..., _ArgTypes...>,
|
|
is_invocable_r<_Res, _Tps..., _ArgTypes...>>::value;
|
|
|
|
public:
|
|
/// Target and bound object is function pointed by parameter.
|
|
template<typename _Fn>
|
|
requires is_function_v<_Fn> && __is_invocable_using<_Fn*>
|
|
function_ref(_Fn* __fn) noexcept
|
|
{
|
|
__glibcxx_assert(__fn != nullptr);
|
|
_M_invoke = _Invoker::template _S_ptrs<_Fn*>();
|
|
_M_init(__fn);
|
|
}
|
|
|
|
/// Target and bound object is object referenced by parameter.
|
|
template<typename _Fn, typename _Vt = remove_reference_t<_Fn>>
|
|
requires (!is_same_v<remove_cv_t<_Vt>, function_ref>)
|
|
&& (!is_member_pointer_v<_Vt>)
|
|
// We deviate from standard by having this condition, that forces
|
|
// function references to use _Fn* constructors. This simplies
|
|
// implementation and provide better diagnostic when used in
|
|
// constant expression (above constructor is not constexpr).
|
|
&& (!is_function_v<_Vt>)
|
|
&& __is_invocable_using<_Vt _GLIBCXX_MOF_CV&>
|
|
constexpr
|
|
function_ref(_Fn&& __f) noexcept
|
|
{
|
|
using _Fd = remove_cv_t<_Vt>;
|
|
if constexpr (__is_std_op_wrapper<_Fd>)
|
|
{
|
|
_M_invoke = _Invoker::template _S_nttp<_Fd{}>;
|
|
_M_ptrs._M_obj = nullptr;
|
|
}
|
|
else if constexpr (requires (_ArgTypes&&... __args) {
|
|
_Fd::operator()(std::forward<_ArgTypes>(__args)...);
|
|
})
|
|
{
|
|
_M_invoke = _Invoker::template _S_static<_Fd>;
|
|
_M_ptrs._M_obj = nullptr;
|
|
}
|
|
else
|
|
{
|
|
_M_invoke = _Invoker::template _S_ptrs<_Vt _GLIBCXX_MOF_CV&>();
|
|
_M_init(std::addressof(__f));
|
|
}
|
|
}
|
|
|
|
// _GLIBCXX_RESOLVE_LIB_DEFECTS
|
|
// 4256. Incorrect constrains for function_ref constructors from nontype
|
|
/// Target object is __fn. There is no bound object.
|
|
template<auto __fn>
|
|
requires __is_invocable_using<const decltype(__fn)&>
|
|
constexpr
|
|
function_ref(nontype_t<__fn>) noexcept
|
|
{
|
|
using _Fn = remove_cv_t<decltype(__fn)>;
|
|
if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
|
|
static_assert(__fn != nullptr);
|
|
|
|
_M_invoke = &_Invoker::template _S_nttp<__fn>;
|
|
_M_ptrs._M_obj = nullptr;
|
|
}
|
|
|
|
/// Target object is equivalent to std::bind_front<_fn>(std::ref(__ref)).
|
|
/// Bound object is object referenced by second parameter.
|
|
template<auto __fn, typename _Up, typename _Td = remove_reference_t<_Up>>
|
|
requires (!is_rvalue_reference_v<_Up&&>)
|
|
&& __is_invocable_using<const decltype(__fn)&, _Td _GLIBCXX_MOF_CV&>
|
|
constexpr
|
|
function_ref(nontype_t<__fn>, _Up&& __ref) noexcept
|
|
{
|
|
using _Fn = remove_cv_t<decltype(__fn)>;
|
|
if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
|
|
static_assert(__fn != nullptr);
|
|
|
|
if constexpr (is_member_pointer_v<_Fn>
|
|
&& same_as<_Td, typename __inv_unwrap<_Td>::type>)
|
|
// 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>;
|
|
else
|
|
_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).
|
|
/// Bound object is object pointed by second parameter (if any).
|
|
template<auto __fn, typename _Td>
|
|
requires __is_invocable_using<const decltype(__fn)&, _Td _GLIBCXX_MOF_CV*>
|
|
constexpr
|
|
function_ref(nontype_t<__fn>, _Td _GLIBCXX_MOF_CV* __ptr) noexcept
|
|
{
|
|
using _Fn = remove_cv_t<decltype(__fn)>;
|
|
if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
|
|
static_assert(__fn != nullptr);
|
|
if constexpr (is_member_pointer_v<_Fn>)
|
|
__glibcxx_assert(__ptr != nullptr);
|
|
|
|
_M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td _GLIBCXX_MOF_CV>;
|
|
_M_init(__ptr);
|
|
}
|
|
|
|
template<typename _Tp>
|
|
requires (!is_same_v<_Tp, function_ref>)
|
|
&& (!is_pointer_v<_Tp>) && (!__is_nontype_v<_Tp>)
|
|
function_ref&
|
|
operator=(_Tp) = delete;
|
|
|
|
/** Invoke the target object.
|
|
*
|
|
* The bound object will be invoked using the supplied arguments,
|
|
* and as const or non-const, as dictated by the template arguments
|
|
* of the `function_ref` specialization.
|
|
*/
|
|
_Res
|
|
operator()(_ArgTypes... __args) const noexcept(_Noex)
|
|
{ return _M_invoke(_M_ptrs, std::forward<_ArgTypes>(__args)...); }
|
|
|
|
private:
|
|
template<typename _Tp>
|
|
constexpr void
|
|
_M_init(_Tp* __ptr) noexcept
|
|
{
|
|
if constexpr (is_function_v<_Tp>)
|
|
_M_ptrs._M_func = reinterpret_cast<void(*)()>(__ptr);
|
|
else
|
|
_M_ptrs._M_obj = __ptr;
|
|
}
|
|
|
|
typename _Invoker::__ptrs_func_t _M_invoke;
|
|
__polyfunc::_Ptrs _M_ptrs;
|
|
};
|
|
|
|
#undef _GLIBCXX_MOF_CV
|
|
|
|
_GLIBCXX_END_NAMESPACE_VERSION
|
|
} // namespace std
|