Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c998557
status quo coverage
AlexGuteniev Oct 25, 2025
c1b8115
expected after the change
AlexGuteniev Oct 25, 2025
a8114cd
Do avoid one copying
AlexGuteniev Oct 25, 2025
2521460
sort better
AlexGuteniev Oct 25, 2025
7f62d36
template not inline
AlexGuteniev Oct 25, 2025
668f3ad
exactly the sane signature
AlexGuteniev Oct 25, 2025
d317649
missing newline
AlexGuteniev Oct 26, 2025
aa745c2
Let's be friends 🐱
AlexGuteniev Oct 26, 2025
4e36558
selective friendship for now
AlexGuteniev Oct 28, 2025
6d8de91
yesexcept
AlexGuteniev Oct 28, 2025
97dc49c
Merge branch 'microsoft:main' into unwrap
AlexGuteniev Oct 31, 2025
544c670
Use extended `friend`.
StephanTLavavej Nov 4, 2025
207d8ef
Fix comments.
StephanTLavavej Nov 4, 2025
ca4dd7d
Clearly separate `_WIN64` codepaths.
StephanTLavavej Nov 4, 2025
d45a91d
There's no invoke (yet)
AlexGuteniev Nov 5, 2025
6ac3a87
Fuse small and large `_inv` to have fewer of them
AlexGuteniev Nov 5, 2025
889cc4e
organize tests
AlexGuteniev Nov 5, 2025
131bd44
nulls
AlexGuteniev Nov 5, 2025
3f66c0b
alignment check
AlexGuteniev Nov 5, 2025
20c683f
count allocations
AlexGuteniev Nov 5, 2025
f71a0d2
Win32
AlexGuteniev Nov 5, 2025
1b2a9c6
format
AlexGuteniev Nov 5, 2025
194d7ba
tests cleanup
AlexGuteniev Nov 6, 2025
aa8e4da
even more const
AlexGuteniev Nov 6, 2025
0a88f38
no std::
AlexGuteniev Nov 6, 2025
a90f9a0
Include more headers.
StephanTLavavej Nov 11, 2025
d5c0f2c
abort() => assert(false), catch by const ref.
StephanTLavavej Nov 11, 2025
1d30d81
Construct alloc_checker temporaries with braces.
StephanTLavavej Nov 11, 2025
e2f67cf
test_wrapped_call (2 args) => test_wrapped_null
StephanTLavavej Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 153 additions & 42 deletions stl/inc/functional
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,11 @@ private:
_Callable _Callee;
};

#if _HAS_CXX23
template <class _Rx, bool _Noexcept, class... _Types>
class _Move_only_function_base;
#endif // _HAS_CXX23

template <class _Ret, class... _Types>
class _Func_class : public _Arg_types<_Types...> {
public:
Expand Down Expand Up @@ -1097,6 +1102,10 @@ protected:
#endif // _HAS_STATIC_RTTI

private:
#if _HAS_CXX23
friend _Move_only_function_base<_Ret, false, _Types...>;
#endif // _HAS_CXX23

bool _Local() const noexcept { // test for locally stored copy of object
return _Getimpl() == static_cast<const void*>(&_Mystorage);
}
Expand Down Expand Up @@ -1370,6 +1379,8 @@ _NODISCARD bool operator!=(nullptr_t, const function<_Fty>& _Other) noexcept {
#endif // !_HAS_CXX20

#if _HAS_CXX23
enum class _Function_storage_mode { _Small, _Large };

// _Move_only_function_data is defined as an array of pointers.
// The first element is always a pointer to _Move_only_function_base::_Impl_t; it emulates a vtable pointer.
// The other pointers are used as storage for a small functor;
Expand All @@ -1392,16 +1403,15 @@ union alignas(max_align_t) _Move_only_function_data {
return &_Data + _Buf_offset<_Fn>;
}

template <class _Fn>
_NODISCARD _Fn* _Small_fn_ptr() const noexcept {
// cast away const to avoid complication of const propagation to here;
// const correctness is still enforced by _Move_only_function_call specializations.
return static_cast<_Fn*>(const_cast<_Move_only_function_data*>(this)->_Buf_ptr<_Fn>());
}

template <class _Fn>
_NODISCARD _Fn* _Large_fn_ptr() const noexcept {
return static_cast<_Fn*>(_Pointers[1]);
template <class _Fn, _Function_storage_mode _Mode>
_NODISCARD _Fn* _Fn_ptr() const noexcept {
if constexpr (_Mode == _Function_storage_mode::_Small) {
// cast away const to avoid complication of const propagation to here;
// const correctness is still enforced by _Move_only_function_call specializations.
return static_cast<_Fn*>(const_cast<_Move_only_function_data*>(this)->_Buf_ptr<_Fn>());
} else {
return static_cast<_Fn*>(_Pointers[1]);
}
}

void _Set_large_fn_ptr(void* const _Value) noexcept {
Expand All @@ -1423,27 +1433,28 @@ template <class _Rx, class... _Types>
_STL_UNREACHABLE; // no return value available for "continue on error"
}

template <class _Vt, class _VtInvQuals, class _Rx, bool _Noex, class... _Types>
_NODISCARD _Rx __stdcall _Function_inv_small(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) {
if constexpr (is_void_v<_Rx>) {
(void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Small_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...);
} else {
return _STD invoke(static_cast<_VtInvQuals>(*_Self._Small_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...);
}
template <class _Rx, class... _Types>
[[noreturn]] _Rx __stdcall _Function_old_not_callable(const _Move_only_function_data&, _Types&&...) {
_Xbad_function_call();
Comment thread
AlexGuteniev marked this conversation as resolved.
}

template <class _Vt, class _VtInvQuals, class _Rx, bool _Noex, class... _Types>
_NODISCARD _Rx __stdcall _Function_inv_large(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) {
template <class _Vt, class _VtInvQuals, _Function_storage_mode _Mode, class _Rx, bool _Noex, class... _Types>
_NODISCARD _Rx __stdcall _Function_inv(const _Move_only_function_data& _Self, _Types&&... _Args) noexcept(_Noex) {
if constexpr (is_void_v<_Rx>) {
(void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Large_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...);
(void) _STD invoke(static_cast<_VtInvQuals>(*_Self._Fn_ptr<_Vt, _Mode>()), _STD forward<_Types>(_Args)...);
} else {
return _STD invoke(static_cast<_VtInvQuals>(*_Self._Large_fn_ptr<_Vt>()), _STD forward<_Types>(_Args)...);
return _STD invoke(static_cast<_VtInvQuals>(*_Self._Fn_ptr<_Vt, _Mode>()), _STD forward<_Types>(_Args)...);
}
}

template <class _Fn, _Function_storage_mode _Mode, class _Rx, class... _Types>
_NODISCARD _Rx __stdcall _Function_inv_old(const _Move_only_function_data& _Self, _Types&&... _Args) {
return _Self._Fn_ptr<_Fn, _Mode>()->_Do_call(_STD forward<_Types>(_Args)...);
}

template <class _Vt>
void __stdcall _Function_move_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept {
const auto _Src_fn_ptr = _Src._Small_fn_ptr<_Vt>();
const auto _Src_fn_ptr = _Src._Fn_ptr<_Vt, _Function_storage_mode::_Small>();
::new (_Self._Buf_ptr<_Vt>()) _Vt(_STD move(*_Src_fn_ptr));
_Src_fn_ptr->~_Vt();
_Self._Impl = _Src._Impl;
Expand All @@ -1458,28 +1469,57 @@ inline void __stdcall _Function_move_large(_Move_only_function_data& _Self, _Mov
_CSTD memcpy(&_Self._Data, &_Src._Data, _Minimum_function_size); // Copy Impl* and functor data
}

#ifdef _WIN64
template <class _Fn>
void __stdcall _Function_move_old_small(_Move_only_function_data& _Self, _Move_only_function_data& _Src) noexcept {
_Fn* const _Old_fn_impl = _Src._Fn_ptr<_Fn, _Function_storage_mode::_Small>();
_Old_fn_impl->_Move(_Self._Buf_ptr<void*>());
_Old_fn_impl->_Delete_this(false);
_Self._Impl = _Src._Impl;
}
#endif // ^^^ 64-bit ^^^

template <class _Vt>
void __stdcall _Function_destroy_small(_Move_only_function_data& _Self) noexcept {
_Self._Small_fn_ptr<_Vt>()->~_Vt();
_Self._Fn_ptr<_Vt, _Function_storage_mode::_Small>()->~_Vt();
}

inline void __stdcall _Function_deallocate_large_default_aligned(_Move_only_function_data& _Self) noexcept {
::operator delete(_Self._Large_fn_ptr<void>());
::operator delete(_Self._Fn_ptr<void, _Function_storage_mode::_Large>());
}

template <class _Fn>
void __stdcall _Function_destroy_old_large(_Move_only_function_data& _Self) noexcept {
_Self._Fn_ptr<_Fn, _Function_storage_mode::_Large>()->_Delete_this(true);
}

#ifdef _WIN64
template <class _Fn>
void __stdcall _Function_destroy_old_small(_Move_only_function_data& _Self) noexcept {
_Self._Fn_ptr<_Fn, _Function_storage_mode::_Small>()->_Delete_this(false);
}
#else // ^^^ 64-bit / 32-bit vvv
template <class _Fn>
void __stdcall _Function_destroy_old_small_as_large(_Move_only_function_data& _Self) noexcept {
_Fn* const _Old_fn_impl = _Self._Fn_ptr<_Fn, _Function_storage_mode::_Large>();
_Old_fn_impl->_Delete_this(false);
::operator delete(static_cast<void*>(_Old_fn_impl));
Comment thread
StephanTLavavej marked this conversation as resolved.
}
#endif // ^^^ 32-bit ^^^

template <size_t _Align>
void __stdcall _Function_deallocate_large_overaligned(_Move_only_function_data& _Self) noexcept {
_STL_INTERNAL_STATIC_ASSERT(_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__);
#ifdef __cpp_aligned_new
::operator delete(_Self._Large_fn_ptr<void>(), align_val_t{_Align});
::operator delete(_Self._Fn_ptr<void, _Function_storage_mode::_Large>(), align_val_t{_Align});
#else // ^^^ defined(__cpp_aligned_new) / !defined(__cpp_aligned_new) vvv
::operator delete(_Self._Large_fn_ptr<void>());
::operator delete(_Self._Fn_ptr<void, _Function_storage_mode::_Large>());
#endif // ^^^ !defined(__cpp_aligned_new) ^^^
}

template <class _Vt>
void __stdcall _Function_destroy_large(_Move_only_function_data& _Self) noexcept {
const auto _Pfn = _Self._Large_fn_ptr<_Vt>();
const auto _Pfn = _Self._Fn_ptr<_Vt, _Function_storage_mode::_Large>();
_Pfn->~_Vt();
#ifdef __cpp_aligned_new
if constexpr (alignof(_Vt) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
Expand Down Expand Up @@ -1555,6 +1595,17 @@ public:
void(__stdcall* _Destroy)(_Move_only_function_data&) _NOEXCEPT_FNPTR;
};

enum class _Impl_kind {
_Usual = 0,
_Old_fn_null = 1,
_Old_fn_large = 2,
#ifdef _WIN64
_Old_fn_small = 3,
#else // ^^^ 64-bit / 32-bit vvv
_Old_fn_small_as_large = 4,
#endif // ^^^ 32-bit ^^^
};

_Move_only_function_data _Data;

_Move_only_function_base() noexcept = default; // leaves fields uninitialized
Expand All @@ -1568,9 +1619,40 @@ public:
_Data._Impl = nullptr;
}

template <class _Vt, class _Fn>
void _Construct_with_old_fn(_Fn&& _Func) {
const auto _Old_fn_impl = _Func._Getimpl();
if (_Old_fn_impl == nullptr) {
_Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_null, _Vt, void>();
} else if (_Func._Local()) {
#ifdef _WIN64
_STL_INTERNAL_STATIC_ASSERT(alignof(max_align_t) == alignof(void*));
// 64-bit target, can put small function into small move_only_function directly
_Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small, _Vt, void>();
_Old_fn_impl->_Move(_Data._Buf_ptr<void*>());
_Func._Tidy();
#else // ^^^ 64-bit / 32-bit vvv
_STL_INTERNAL_STATIC_ASSERT(alignof(max_align_t) > alignof(void*));
// 32-bit target, cannot put small function into small move_only_function directly
// due to potentially not enough alignment. Allocate large function
void* _Where = ::operator new((_Small_object_num_ptrs - 1) * sizeof(void*));
_Old_fn_impl->_Move(_Where);
_Func._Tidy();

_Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_small_as_large, _Vt, void>();
_Data._Set_large_fn_ptr(_Where);
#endif // ^^^ 32-bit ^^^
} else {
// Just take ownership of the inner impl pointer
_Data._Impl = _Create_impl_ptr<_Impl_kind::_Old_fn_large, _Vt, void>();
_Data._Set_large_fn_ptr(_Old_fn_impl);
_Func._Set(nullptr);
}
}

template <class _Vt, class _VtInvQuals, class... _CTypes>
void _Construct_with_fn(_CTypes&&... _Args) {
_Data._Impl = _Create_impl_ptr<_Vt, _VtInvQuals>();
_Data._Impl = _Create_impl_ptr<_Impl_kind::_Usual, _Vt, _VtInvQuals>();
if constexpr (_Large_function_engaged<_Vt>) {
_Data._Set_large_fn_ptr(_STD _Function_new_large<_Vt>(_STD forward<_CTypes>(_Args)...));
} else {
Expand Down Expand Up @@ -1661,11 +1743,36 @@ public:
return _Ret ? _Ret : &_Null_move_only_function;
}

template <class _Vt, class _VtInvQuals>
template <_Impl_kind _Kind, class _Vt, class _VtInvQuals>
_NODISCARD static constexpr _Impl_t _Create_impl() noexcept {
_Impl_t _Impl{};
if constexpr (_Large_function_engaged<_Vt>) {
_Impl._Invoke = _Function_inv_large<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>;
if constexpr (_Kind != _Impl_kind::_Usual) {
_STL_INTERNAL_STATIC_ASSERT(!_Noexcept);
_STL_INTERNAL_STATIC_ASSERT(is_void_v<_VtInvQuals>);
using _Fn = remove_pointer_t<decltype(_STD declval<_Vt>()._Getimpl())>;
if constexpr (_Kind == _Impl_kind::_Old_fn_null) {
_Impl._Invoke = _Function_old_not_callable<_Rx, _Types...>;
_Impl._Move = nullptr;
_Impl._Destroy = nullptr;
} else if constexpr (_Kind == _Impl_kind::_Old_fn_large) {
_Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Large, _Rx, _Types...>;
_Impl._Move = nullptr;
_Impl._Destroy = _Function_destroy_old_large<_Fn>;
} else {
#ifdef _WIN64
static_assert(_Kind == _Impl_kind::_Old_fn_small);
_Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Small, _Rx, _Types...>;
_Impl._Move = _Function_move_old_small<_Fn>;
_Impl._Destroy = _Function_destroy_old_small<_Fn>;
#else // ^^^ 64-bit / 32-bit vvv
static_assert(_Kind == _Impl_kind::_Old_fn_small_as_large);
_Impl._Invoke = _Function_inv_old<_Fn, _Function_storage_mode::_Large, _Rx, _Types...>;
_Impl._Move = nullptr;
_Impl._Destroy = _Function_destroy_old_small_as_large<_Fn>;
#endif // ^^^ 32-bit ^^^
}
} else if constexpr (_Large_function_engaged<_Vt>) {
_Impl._Invoke = _Function_inv<_Vt, _VtInvQuals, _Function_storage_mode::_Large, _Rx, _Noexcept, _Types...>;
_Impl._Move = nullptr;

if constexpr (is_trivially_destructible_v<_Vt>) {
Expand All @@ -1678,7 +1785,7 @@ public:
_Impl._Destroy = _Function_destroy_large<_Vt>;
}
} else {
_Impl._Invoke = _Function_inv_small<_Vt, _VtInvQuals, _Rx, _Noexcept, _Types...>;
_Impl._Invoke = _Function_inv<_Vt, _VtInvQuals, _Function_storage_mode::_Small, _Rx, _Noexcept, _Types...>;

if constexpr (is_trivially_copyable_v<_Vt> && is_trivially_destructible_v<_Vt>) {
if constexpr ((_Function_small_copy_size<_Vt>) > _Minimum_function_size) {
Expand All @@ -1699,9 +1806,9 @@ public:
return _Impl;
}

template <class _Vt, class _VtInvQuals>
template <_Impl_kind _Kind, class _Vt, class _VtInvQuals>
_NODISCARD static const _Impl_t* _Create_impl_ptr() noexcept {
static constexpr _Impl_t _Impl = _Create_impl<_Vt, _VtInvQuals>();
static constexpr _Impl_t _Impl = _Create_impl<_Kind, _Vt, _VtInvQuals>();
return &_Impl;
}
};
Expand Down Expand Up @@ -1932,16 +2039,20 @@ public:
using _Vt = decay_t<_Fn>;
static_assert(is_constructible_v<_Vt, _Fn>, "_Vt should be constructible from _Fn. "
"(N4950 [func.wrap.move.ctor]/6)");

if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt> || _Is_specialization_v<_Vt, move_only_function>) {
if (_Callable == nullptr) {
this->_Reset_to_null();
return;
if constexpr (is_same_v<_Vt, function<_Signature...>>) {
Comment thread
StephanTLavavej marked this conversation as resolved.
this->template _Construct_with_old_fn<_Vt>(_STD forward<_Fn>(_Callable));
} else {
if constexpr (is_member_pointer_v<_Vt> || is_pointer_v<_Vt>
|| _Is_specialization_v<_Vt, move_only_function>) {
if (_Callable == nullptr) {
this->_Reset_to_null();
return;
}
}
}

using _VtInvQuals = _Call::template _VtInvQuals<_Vt>;
this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable));
using _VtInvQuals = _Call::template _VtInvQuals<_Vt>;
this->template _Construct_with_fn<_Vt, _VtInvQuals>(_STD forward<_Fn>(_Callable));
}
}

template <class _Fn, class... _CTypes>
Expand Down
1 change: 1 addition & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ tests\GH_005315_destructor_tombstones
tests\GH_005402_string_with_volatile_range
tests\GH_005421_vector_algorithms_integer_class_type_iterator
tests\GH_005472_do_not_overlap
tests\GH_005504_avoid_function_call_wrapping
tests\GH_005546_containers_size_type_cast
tests\GH_005553_regex_character_translation
tests\GH_005768_pow_accuracy
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\usual_latest_matrix.lst
Loading