Files
gcc/libstdc++-v3/include/bits/funcwrap.h
Tomasz Kamiński b58cf25443 libstdc++: Make function_ref non-dangling for stateless wrappers
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>
2026-02-11 17:54:09 +01:00

632 lines
18 KiB
C++

// Implementation of std::move_only_function, std::copyable_function
// and 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/funcwrap.h
* This is an internal header file, included by other library headers.
* Do not attempt to use it directly. @headername{functional}
*/
#ifndef _GLIBCXX_FUNCWRAP_H
#define _GLIBCXX_FUNCWRAP_H 1
#ifdef _GLIBCXX_SYSHDR
#pragma GCC system_header
#endif
#include <bits/version.h>
#if __glibcxx_move_only_function || __glibcxx_copyable_function || __glibcxx_function_ref
#include <bits/invoke.h>
#include <bits/utility.h>
#if __glibcxx_function_ref
#include <bits/stl_function.h>
#endif
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
/// @cond undocumented
template<typename _Tp>
inline constexpr bool __is_polymorphic_function_v = false;
namespace __polyfunc
{
union _Ptrs
{
const void* _M_obj;
void (*_M_func)();
};
template<typename _Tp>
[[__gnu__::__always_inline__]]
constexpr auto*
__cast_to(_Ptrs __ptrs) noexcept
{
using _Td = remove_reference_t<_Tp>;
if constexpr (is_function_v<_Td>)
return reinterpret_cast<_Td*>(__ptrs._M_func);
else if constexpr (is_const_v<_Td>)
return static_cast<_Td*>(__ptrs._M_obj);
else
return static_cast<_Td*>(const_cast<void*>(__ptrs._M_obj));
}
struct _Storage
{
void* _M_addr() noexcept { return &_M_bytes[0]; }
void const* _M_addr() const noexcept { return &_M_bytes[0]; }
template<typename _Tp>
static consteval bool
_S_stored_locally() noexcept
{
return sizeof(_Tp) <= sizeof(_Storage)
&& alignof(_Tp) <= alignof(_Storage)
&& is_nothrow_move_constructible_v<_Tp>;
}
template<typename _Tp, typename... _Args>
static consteval bool
_S_nothrow_init() noexcept
{
if constexpr (_S_stored_locally<_Tp>())
return is_nothrow_constructible_v<_Tp, _Args...>;
return false;
}
template<typename _Tp, typename... _Args>
void
_M_init(_Args&&... __args) noexcept(_S_nothrow_init<_Tp, _Args...>())
{
if constexpr (is_function_v<remove_pointer_t<_Tp>>)
{
static_assert( sizeof...(__args) <= 1 );
// __args can have up to one element, returns nullptr if empty.
_Tp __func = (nullptr, ..., __args);
_M_ptrs._M_func = reinterpret_cast<void(*)()>(__func);
}
else if constexpr (!_S_stored_locally<_Tp>())
_M_ptrs._M_obj = new _Tp(std::forward<_Args>(__args)...);
else
::new (_M_addr()) _Tp(std::forward<_Args>(__args)...);
}
// We want to have enough space to store a simple delegate type.
struct _Delegate { void (_Storage::*__pfm)(); _Storage* __obj; };
union {
_Ptrs _M_ptrs;
alignas(_Delegate) alignas(void(*)())
unsigned char _M_bytes[sizeof(_Delegate)];
};
};
template<bool _Noex, typename _Ret, typename... _Args>
struct _Base_invoker
{
using _Signature = _Ret(*)(_Args...) noexcept(_Noex);
using __storage_func_t = _Ret(*)(const _Storage&, _Args...) noexcept(_Noex);
template<typename _Tp>
static consteval __storage_func_t
_S_storage()
{ return &_S_call_storage<_Adjust_target<_Tp>>; }
using __ptrs_func_t = _Ret(*)(_Ptrs, _Args...) noexcept(_Noex);
template<typename _Tp>
static consteval __ptrs_func_t
_S_ptrs()
{ return &_S_call_ptrs<_Adjust_target<_Tp>>; }
#ifdef __glibcxx_function_ref // C++ >= 26
template<typename _Fn>
static _Ret
_S_static(_Ptrs, _Args... __args) noexcept(_Noex)
{ return _Fn::operator()(std::forward<_Args>(__args)...); }
template<auto __fn>
static _Ret
_S_nttp(_Ptrs, _Args... __args) noexcept(_Noex)
{ return std::__invoke_r<_Ret>(__fn, std::forward<_Args>(__args)...); }
template<auto __fn, typename _Tp>
static _Ret
_S_bind_ptr(_Ptrs __ptrs, _Args... __args) noexcept(_Noex)
{
auto* __p = __polyfunc::__cast_to<_Tp>(__ptrs);
return std::__invoke_r<_Ret>(__fn, __p,
std::forward<_Args>(__args)...);
}
template<auto __fn, typename _Ref>
static _Ret
_S_bind_ref(_Ptrs __ptrs, _Args... __args) noexcept(_Noex)
{
auto* __p = __polyfunc::__cast_to<_Ref>(__ptrs);
return std::__invoke_r<_Ret>(__fn, static_cast<_Ref>(*__p),
std::forward<_Args>(__args)...);
}
#endif // __glibcxx_function_ref
private:
template<typename _Tp, typename _Td = remove_cvref_t<_Tp>>
using _Adjust_target =
__conditional_t<is_pointer_v<_Td> || is_member_pointer_v<_Td>, _Td, _Tp>;
template<typename _Tp>
static _Ret
_S_call_storage(const _Storage& __ref, _Args... __args) noexcept(_Noex)
{
_Ptrs __ptrs;
if constexpr (is_function_v<remove_pointer_t<_Tp>>)
__ptrs._M_func = __ref._M_ptrs._M_func;
else if constexpr (!_Storage::_S_stored_locally<remove_cvref_t<_Tp>>())
__ptrs._M_obj = __ref._M_ptrs._M_obj;
else
__ptrs._M_obj = __ref._M_addr();
return _S_call_ptrs<_Tp>(__ptrs, std::forward<_Args>(__args)...);
}
template<typename _Tp>
static _Ret
_S_call_ptrs(_Ptrs __ptrs, _Args... __args) noexcept(_Noex)
{
if constexpr (is_function_v<remove_pointer_t<_Tp>>)
return std::__invoke_r<_Ret>(reinterpret_cast<_Tp>(__ptrs._M_func),
std::forward<_Args>(__args)...);
else
{
auto* __p = __polyfunc::__cast_to<_Tp>(__ptrs);
return std::__invoke_r<_Ret>(static_cast<_Tp>(*__p),
std::forward<_Args>(__args)...);
}
}
};
template<typename _Tp>
consteval bool
__pass_by_value()
{
// n.b. sizeof(Incomplete&) is ill-formed for incomplete types,
// so we check is_reference_v first.
if constexpr (is_reference_v<_Tp> || is_scalar_v<_Tp>)
return true;
else
// n.b. we already asserted that types are complete in wrappers,
// avoid triggering additional errors from this function.
if constexpr (std::__is_complete_or_unbounded(__type_identity<_Tp>()))
if constexpr (sizeof(_Tp) <= 2 * sizeof(void*))
return is_trivially_move_constructible_v<_Tp>
&& is_trivially_destructible_v<_Tp>;
return false;
}
template<typename _Tp>
using __param_t = __conditional_t<__pass_by_value<_Tp>(), _Tp, _Tp&&>;
template<bool _Noex, typename _Ret, typename... _Args>
using _Invoker = _Base_invoker<_Noex, remove_cv_t<_Ret>, __param_t<_Args>...>;
template<typename _Func>
auto&
__invoker_of(_Func& __f) noexcept
{ return __f._M_invoke; }
template<typename _Func>
auto&
__base_of(_Func& __f) noexcept
{ return static_cast<__like_t<_Func&, typename _Func::_Base>>(__f); }
template<typename _Src, typename _Dst>
consteval bool
__is_invoker_convertible() noexcept
{
if constexpr (requires { typename _Src::_Signature; })
return is_convertible_v<typename _Src::_Signature,
typename _Dst::_Signature>;
else
return false;
}
#if __glibcxx_move_only_function || __glibcxx_copyable_function
struct _Manager
{
enum class _Op
{
// saves address of entity in *__src to __target._M_ptrs,
_Address,
// moves entity stored in *__src to __target, __src becomes empty
_Move,
// copies entity stored in *__src to __target, supported only if
// _ProvideCopy is specified.
_Copy,
// destroys entity stored in __target, __src is ignoring
_Destroy,
};
// A function that performs operation __op on the __target and possibly __src.
using _Func = void (*)(_Op __op, _Storage& __target, const _Storage* __src);
// The no-op manager function for objects with no target.
static void _S_empty(_Op, _Storage&, const _Storage*) noexcept { }
template<bool _ProvideCopy, typename _Tp>
consteval static auto
_S_select()
{
if constexpr (is_function_v<remove_pointer_t<_Tp>>)
return &_S_func;
else if constexpr (!_Storage::_S_stored_locally<_Tp>())
return &_S_ptr<_ProvideCopy, _Tp>;
else if constexpr (is_trivially_copyable_v<_Tp>)
return &_S_trivial;
else
return &_S_local<_ProvideCopy, _Tp>;
}
private:
static void
_S_func(_Op __op, _Storage& __target, const _Storage* __src) noexcept
{
switch (__op)
{
case _Op::_Address:
case _Op::_Move:
case _Op::_Copy:
__target._M_ptrs._M_func = __src->_M_ptrs._M_func;
return;
case _Op::_Destroy:
return;
}
}
static void
_S_trivial(_Op __op, _Storage& __target, const _Storage* __src) noexcept
{
switch (__op)
{
case _Op::_Address:
__target._M_ptrs._M_obj = __src->_M_addr();
return;
case _Op::_Move:
case _Op::_Copy:
// N.B. Creating _Storage starts lifetime of _M_bytes char array,
// that implicitly creates, amongst other, all possible trivially
// copyable objects, so we copy any object present in __src._M_bytes.
::new (&__target) _Storage(*__src);
return;
case _Op::_Destroy:
return;
}
}
template<bool _Provide_copy, typename _Tp>
static void
_S_local(_Op __op, _Storage& __target, const _Storage* __src)
noexcept(!_Provide_copy)
{
switch (__op)
{
case _Op::_Address:
__target._M_ptrs._M_obj = __src->_M_addr();
return;
case _Op::_Move:
{
_Tp* __obj = static_cast<_Tp*>(const_cast<void*>(__src->_M_addr()));
::new(__target._M_addr()) _Tp(std::move(*__obj));
__obj->~_Tp();
}
return;
case _Op::_Destroy:
static_cast<_Tp*>(__target._M_addr())->~_Tp();
return;
case _Op::_Copy:
if constexpr (_Provide_copy)
{
auto* __obj = static_cast<const _Tp*>(__src->_M_addr());
::new (__target._M_addr()) _Tp(*__obj);
return;
}
__builtin_unreachable();
}
}
template<bool _Provide_copy, typename _Tp>
static void
_S_ptr(_Op __op, _Storage& __target, const _Storage* __src)
noexcept(!_Provide_copy)
{
switch (__op)
{
case _Op::_Address:
case _Op::_Move:
__target._M_ptrs._M_obj = __src->_M_ptrs._M_obj;
return;
case _Op::_Destroy:
delete static_cast<const _Tp*>(__target._M_ptrs._M_obj);
return;
case _Op::_Copy:
if constexpr (_Provide_copy)
{
auto* __obj = static_cast<const _Tp*>(__src->_M_ptrs._M_obj);
__target._M_ptrs._M_obj = new _Tp(*__obj);
return;
}
__builtin_unreachable();
}
}
};
class _Mo_base
{
protected:
_Mo_base() noexcept
: _M_manage(_Manager::_S_empty)
{ }
_Mo_base(_Mo_base&& __x) noexcept
{ _M_move(__x); }
template<typename _Tp, typename... _Args>
static consteval bool
_S_nothrow_init() noexcept
{ return _Storage::_S_nothrow_init<_Tp, _Args...>(); }
template<typename _Tp, typename... _Args>
void
_M_init(_Args&&... __args)
noexcept(_S_nothrow_init<_Tp, _Args...>())
{
_M_storage._M_init<_Tp>(std::forward<_Args>(__args)...);
_M_manage = _Manager::_S_select<false, _Tp>();
}
void
_M_move(_Mo_base& __x) noexcept
{
using _Op = _Manager::_Op;
_M_manage = std::__exchange(__x._M_manage, _Manager::_S_empty);
_M_manage(_Op::_Move, _M_storage, &__x._M_storage);
}
_Mo_base&
operator=(_Mo_base&& __x) noexcept
{
_M_destroy();
_M_move(__x);
return *this;
}
void
_M_reset() noexcept
{
_M_destroy();
_M_manage = _Manager::_S_empty;
}
void _M_destroy() noexcept
{ _M_manage(_Manager::_Op::_Destroy, _M_storage, nullptr); }
~_Mo_base()
{ _M_destroy(); }
void
swap(_Mo_base& __x) noexcept
{
using _Op = _Manager::_Op;
// Order of operations here is more efficient if __x is empty.
_Storage __s;
__x._M_manage(_Op::_Move, __s, &__x._M_storage);
_M_manage(_Op::_Move, __x._M_storage, &_M_storage);
__x._M_manage(_Op::_Move, _M_storage, &__s);
std::swap(_M_manage, __x._M_manage);
}
_Manager::_Func _M_manage;
_Storage _M_storage;
};
#endif // __glibcxx_copyable_function || __glibcxx_copyable_function
} // namespace __polyfunc
/// @endcond
#ifdef __glibcxx_move_only_function // C++ >= 23 && HOSTED
template<typename... _Signature>
class move_only_function; // not defined
/// @cond undocumented
template<typename _Tp>
constexpr bool __is_polymorphic_function_v<move_only_function<_Tp>> = true;
namespace __detail::__variant
{
template<typename> struct _Never_valueless_alt; // see <variant>
// Provide the strong exception-safety guarantee when emplacing a
// move_only_function into a variant.
template<typename... _Signature>
struct _Never_valueless_alt<std::move_only_function<_Signature...>>
: true_type
{ };
} // namespace __detail::__variant
/// @endcond
#endif // __glibcxx_move_only_function
#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED
/// @cond undocumented
namespace __polyfunc
{
class _Cpy_base : public _Mo_base
{
protected:
_Cpy_base() = default;
template<typename _Tp, typename... _Args>
void
_M_init(_Args&&... __args)
noexcept(_S_nothrow_init<_Tp, _Args...>())
{
_M_storage._M_init<_Tp>(std::forward<_Args>(__args)...);
_M_manage = _Manager::_S_select<true, _Tp>();
}
void
_M_copy(_Cpy_base const& __x)
{
using _Op = _Manager::_Op;
__x._M_manage(_Op::_Copy, _M_storage, &__x._M_storage);
_M_manage = __x._M_manage;
}
_Cpy_base(_Cpy_base&&) = default;
_Cpy_base(_Cpy_base const& __x)
: _Mo_base()
{ _M_copy(__x); }
_Cpy_base&
operator=(_Cpy_base&&) = default;
_Cpy_base&
// Needs to use copy and swap for exception guarantees.
operator=(_Cpy_base const&) = delete;
};
} // namespace __polyfunc
/// @endcond
template<typename... _Signature>
class copyable_function; // not defined
template<typename _Tp>
constexpr bool __is_polymorphic_function_v<copyable_function<_Tp>> = true;
namespace __detail::__variant
{
template<typename> struct _Never_valueless_alt; // see <variant>
// Provide the strong exception-safety guarantee when emplacing a
// copyable_function into a variant.
template<typename... _Signature>
struct _Never_valueless_alt<std::copyable_function<_Signature...>>
: true_type
{ };
} // namespace __detail::__variant
#endif // __glibcxx_copyable_function
#ifdef __glibcxx_function_ref // C++ >= 26
/// @cond undocumented
namespace __polyfunc
{
template<typename _Sig>
struct __skip_first_arg;
// Additional partial specializations are defined in bits/funcref_impl.h
template<bool _Noex, typename _Ret, typename _Arg, typename... _Args>
struct __skip_first_arg<_Ret(*)(_Arg, _Args...) noexcept(_Noex)>
{ using type = _Ret(_Args...) noexcept(_Noex); };
// Returns a function pointer to signature to be used with function_ref, or void.
template<typename _Fn, typename _Tr>
consteval auto
__deduce_funcref()
{
if constexpr (is_member_object_pointer_v<_Fn>)
{
if constexpr (is_invocable_v<_Fn, _Tr>)
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 4425. CTAD function_ref from data member pointer should produce
// noexcept signature
return static_cast<invoke_result_t<_Fn, _Tr>(*)() noexcept>(nullptr);
}
else if constexpr (requires { typename __skip_first_arg<_Fn>::type; })
return static_cast<__skip_first_arg<_Fn>::type*>(nullptr);
}
} // namespace __polyfunc
/// @endcond
template<typename... _Signature>
class function_ref; // not defined
template<typename _Fn>
requires is_function_v<_Fn>
function_ref(_Fn*) -> function_ref<_Fn>;
template<auto __f, class _Fn = remove_pointer_t<decltype(__f)>>
requires is_function_v<_Fn>
function_ref(nontype_t<__f>) -> function_ref<_Fn>;
template<auto __f, typename _Tp,
typename _SignaturePtr =
decltype(__polyfunc::__deduce_funcref<decltype(__f), _Tp&>())>
requires (!is_void_v<_SignaturePtr>)
function_ref(nontype_t<__f>, _Tp&&)
-> function_ref<remove_pointer_t<_SignaturePtr>>;
#endif // __glibcxx_function_ref
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#ifdef __glibcxx_move_only_function // C++ >= 23 && HOSTED
#include "mofunc_impl.h"
#define _GLIBCXX_MOF_CV const
#include "mofunc_impl.h"
#define _GLIBCXX_MOF_REF &
#include "mofunc_impl.h"
#define _GLIBCXX_MOF_REF &&
#include "mofunc_impl.h"
#define _GLIBCXX_MOF_CV const
#define _GLIBCXX_MOF_REF &
#include "mofunc_impl.h"
#define _GLIBCXX_MOF_CV const
#define _GLIBCXX_MOF_REF &&
#include "mofunc_impl.h"
#endif // __glibcxx_move_only_function
#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED
#include "cpyfunc_impl.h"
#define _GLIBCXX_MOF_CV const
#include "cpyfunc_impl.h"
#define _GLIBCXX_MOF_REF &
#include "cpyfunc_impl.h"
#define _GLIBCXX_MOF_REF &&
#include "cpyfunc_impl.h"
#define _GLIBCXX_MOF_CV const
#define _GLIBCXX_MOF_REF &
#include "cpyfunc_impl.h"
#define _GLIBCXX_MOF_CV const
#define _GLIBCXX_MOF_REF &&
#include "cpyfunc_impl.h"
#endif // __glibcxx_copyable_function
#ifdef __glibcxx_function_ref // C++ >= 26
#include "funcref_impl.h"
#define _GLIBCXX_MOF_CV const
#include "funcref_impl.h"
#endif // __glibcxx_function_ref
#endif // move_only_function || copyable_function || function_ref
#endif // _GLIBCXX_FUNCWRAP_H