diff --git a/.github/workflows/gh-actions.yml b/.github/workflows/gh-actions.yml new file mode 100644 index 0000000..512d9e0 --- /dev/null +++ b/.github/workflows/gh-actions.yml @@ -0,0 +1,47 @@ +name: Build & Test + +on: + push: + branches: [ master, feat/psi-modernization ] + pull_request: + branches: [ master ] + +jobs: + build: + name: ${{ matrix.config.name }} — ${{ matrix.build_type }} + runs-on: ${{ matrix.config.os }} + timeout-minutes: 20 + + strategy: + fail-fast: false + matrix: + config: + - { os: ubuntu-latest, name: Clang, cc: clang, cxx: clang++, gen: '-G Ninja' } + - { os: macos-26, name: Apple-Clang, cc: clang, cxx: clang++, gen: '-G Ninja' } + build_type: [Debug, Release] + + steps: + - uses: actions/checkout@v4 + + - name: Install LLVM/Clang 22 (Linux) + if: matrix.config.name == 'Clang' + run: | + wget https://apt.llvm.org/llvm.sh + chmod u+x llvm.sh + sudo ./llvm.sh 22 all + echo "CC=/usr/lib/llvm-22/bin/clang" >> $GITHUB_ENV + echo "CXX=/usr/lib/llvm-22/bin/clang++" >> $GITHUB_ENV + + - name: Configure + run: | + cmake -B build ${{ matrix.config.gen }} \ + -DCMAKE_CXX_COMPILER="${CXX:-${{ matrix.config.cxx }}}" \ + -DCMAKE_C_COMPILER="${CC:-${{ matrix.config.cc }}}" \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -S . + + - name: Build & test + run: | + cmake --build build --config ${{ matrix.build_type }} -j + cd build/test + ctest --output-on-failure -C ${{ matrix.build_type }} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dbd9859 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required( VERSION 3.27.0 ) + +project( Psi.Functionoid CXX ) + +################### +## Deps +################### + +file( + DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/get_cpm.cmake +) +set( CPM_SOURCE_CACHE "${CMAKE_CURRENT_BINARY_DIR}/deps" ) + +if ( APPLE ) + set( GIT_EXECUTABLE /usr/bin/git CACHE FILEPATH "Path to git executable" FORCE ) +endif() + +include( ${CMAKE_CURRENT_BINARY_DIR}/cmake/get_cpm.cmake ) + +set( boost_ver boost-1.90.0 ) +CPMAddPackage( "gh:boostorg/static_assert#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/throw_exception#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/config#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/type_traits#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/assert#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/core#${boost_ver}" ) +CPMAddPackage( "gh:boostorg/move#${boost_ver}" ) + +CPMAddPackage( "gh:psiha/config_ex#master" ) +CPMAddPackage( "gh:psiha/build#master" ) + +if ( NOT TARGET Boost::boost ) + add_library( Boost::boost INTERFACE IMPORTED ) + target_link_libraries( Boost::boost INTERFACE + Boost::config Boost::assert Boost::core Boost::move Boost::type_traits + ) + target_include_directories( Boost::boost INTERFACE "${config_ex_SOURCE_DIR}/include" ) +endif() + +if ( ${CMAKE_SYSTEM_NAME} MATCHES "Linux" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) + set( PSI_USE_LINKER "lld" ) +endif() +include( ${build_SOURCE_DIR}/build_options.cmake ) + +set( CMAKE_CXX_STANDARD 26 ) +add_compile_definitions( BOOST_ALL_NO_LIB ) +if ( WIN32 ) + add_compile_definitions( WIN32_LEAN_AND_MEAN NOMINMAX ) +endif() + +################### +## Target +################### + +include( ${CMAKE_CURRENT_LIST_DIR}/functionoid.cmake ) + +################### +## Tests +################### + +if ( PROJECT_IS_TOP_LEVEL ) + enable_testing() + add_subdirectory( test ) +endif() diff --git a/README.md b/README.md index 2cd4ec0..8413acb 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,114 @@ -#### Old notes (before conversion to C++14, renaming to functionoid/callable) - -An alternate boost::function implementation that -- minimizes template and RTTI bloat -- has (much) less runtime overhead (for invocation, copy-construction and assignment) -- provides configurability through policies. - -It is intended to be a drop-in replacement hence the same folder name as the -original. - -For more information see the relevant discussion threads at boost.devel: -http://lists.boost.org/Archives/boost/2010/10/172593.php -http://thread.gmane.org/gmane.comp.lib.boost.devel/194514/focus=195351 -http://article.gmane.org/gmane.comp.lib.boost.devel/196906. - -Related undertakings: -- https://github.com/mamedev/delegates -- http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible -- http://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates -- http://www.codeproject.com/Articles/18389/Fast-C-Delegate-Boost-Function-drop-in-replacement -- http://www.codeproject.com/Articles/13287/Fast-C-Delegate -- http://stackoverflow.com/questions/4298408/5-years-later-is-there-something-better-than-the-fastest-possible-c-delegate -- http://www.gamedev.net/topic/549468-fast-c-delegates/ -- http://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11 -- https://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction -- move-only problem: - - http://lwg.github.io/issues/lwg-defects.html#1287 - - http://stackoverflow.com/questions/25330716/move-only-version-of-stdfunction - - https://groups.google.com/forum/#!topic/comp.lang.c++.moderated/OFAMx695NJg - - https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/hFBaATcX0Wo - -Currently this (alternate) code is based on original code from the -boost::function official 1.43 release (but it has been rebased on the current master -as part of the move from the sandbox to github). For the originial documentation see -http://www.boost.org/doc/libs/1_43_0/doc/html/function.html. - -It was tested (compiling and running real world projects, building Boost and -running Boost regression tests) with the following compilers: - - MSVC++ 8.0 SP1, 10.0 (x86 and x64) - - GCC 4.0.1, 4.2.1, 4.5.1 - - Clang 2.0, 2.8 - - Sun Studio 5.9, 5.10 - - VisualAge 11.1. - - -For the sake of brevity of future commit messages, comments etc. allow me to -add a few terminology definitions. Looking at the boost/tr1::function<> design/ -idea we can identify two 'domains': - - the "front side" or the interface side or the "fixed type" or ABI side. The -interface that the user sees and the code that exists and executes _before_ the -call through a function pointer in the vtable (e.g. the code in operator() -before the invoke call is made, or code in operator= before calls to the manager -are made ...) - - the "back side" or that which stands behind the 'function pointer brick wall'/ -'type erasure' (the actual invoker and the actual manager), which changes with -each assignement, which, at the point of change or creation has all the type -information it wants. - -A few more notes: -- the default interface and behaviour remain the same (current code should work -the same without modification with these changes, "or so it seems to me") -- the empty handler objects are required to satisfy the is_stateless<> -condition (even though the current code should work even w/o this requirement -being satisfied) -- all function objects used with boost::function<> are expected to have a -nothrow destructor -- the swap and non-trivial assignment function do not offer the strong -exception safety guarantee in the special case when one or both of the objects -at stake is or has (if it is a boost::function<> object "holding" some function -object) a function object that fits in the function_buffer but does not have a -nothrow copy constructor and therefore does not have a no-fail move -operation...in this case it can happen that the final move operation (in the -swap procedure) from the tmp object to *this can fail and then the attempt to -restore (move) the original *this from a backup location can also fail leaving -us with no valid object in *this...in this situation the *this function object -is cleared (the EmptyHandler is assigned)...as far as i could tell the original -boost::function<> implementation had the same behaviour... -- a few more ideas of what can be made a matter of policy: size and alignment -of the function buffer (perhaps some library uses only complex targets so it -would benefit if the function_buffer optimization would be eliminated all -together...on the other some other library would benefit if the buffer would be -better aligned or enlarged by "just one int" and then no memory allocation -would take place for that library ...etc...) -- there is the ever present problem of what to return from empty handlers for -non-void and non-default constructible return types... -... +# Psi.Functionoid + +Header-only, policy-driven type erasure for callables. The library descends from +[Boost.Functionoid](https://github.com/psiha/functionoid) (Domagoj Šarić’s +alternate `boost::function`); it is modernized for C++14+ and lives under the +`psi::functionoid` namespace with CMake target `Psi::Functionoid`. + +Two public surfaces: + +| Type | Role | +|------|------| +| **`callable`** | Owning wrapper — configurable `std::function` replacement | +| **`function_ref`** | Non-owning view — configurable `std::function_ref` replacement | + +## Why not `std::function`? + +`std::function` fixed the ergonomics of type-erased callbacks, but the design is +largely frozen: one size fits all (always copyable, fixed SBO, RTTI-based +`target_type`/`target`, empty handler throws). Hot paths pay for features they +never use. + +**Psi.Functionoid `callable`** keeps the familiar “assign anything callable” model +but makes behaviour a **compile-time policy** (`default_traits` and custom +specializations): + +- **SBO tuning** — buffer size and alignment are traits, not hard-coded. +- **Copy / move / destroy levels** — `support_level::{na,supported,nofail,trivial}` + so move-only, noexcept-move, or trivial-destruction paths need no copy vtable + slots (sweater `work_t` uses move-only + trivial dtor). +- **Empty handler** — throw, assert, or no-op on invoke of an empty wrapper. +- **RTTI** — on or off (`default_traits` disables it). +- **Noexcept signature** — trait-driven `noexcept` on `operator()`. +- **Vtable call-site hints** — optional `gnu::pure`, `clang::preserve_most`, … on + invoke/manager function pointers (`detail::vtable_attrs.hpp`). +- **Less bloat** — front/back split inherited from Functionoid: thin fixed ABI + “front”, typed invoker/manager “back”; fewer template instantiations and less + RTTI than classic `boost::function`. + +Typical wins: lower per-call overhead, smaller hot-loop binaries, and wrappers +sized to the embedding project instead of the standard library’s compromise. + +## Compared to the standard “fix attempts” + +C++23 **`std::move_only_function`** and the ongoing **`std::copyable_function`** +proposal address the worst parts of `std::function` (move-only storage, clearer +empty behaviour) but remain **non-configurable**: you pick the standard type, not +policies. You cannot turn off RTTI, shrink the SBO for cache footprint, mark +vtable calls `[[gnu::pure]]`, or select assert-on-empty for debug builds. + +**Psi.Functionoid** is the layer below those typedefs: one `callable` template, +many shapes — including move-only noexcept aliases that match or beat +`std::move_only_function` for scheduler/work-queue use, and copyable +`std_traits` for drop-in `std::function` semantics when you need them. + +## `function_ref` + +Non-owning type-erased reference to a callable with signature `R(Args...)` +(optionally `noexcept`). Same conceptual slot as **`std::function_ref`** +(P2637): pass a callback into an API without allocating or owning it. + +Optimizations beyond a naive vtable indirection: + +1. **Pointer-sized SBO** — if the decayed callable is trivially copyable and + fits in the storage word (`sizeof(void*)`), it is **embedded inline** and + invoked without an extra load. +2. **Borrowed object** — otherwise the ref holds a pointer to the caller’s + callable (lifetime is the caller’s responsibility, as with `function_ref`). + +### Exception tunneling (C / V8 / other `noexcept` APIs) + +Many foreign interfaces only accept **`noexcept` function pointers** — C +callbacks, V8 embedder hooks, etc. C++ callables that may throw cannot be passed +directly. + +When the source callable is not `noexcept`-compatible, `function_ref` wraps it in +an **`exception_tunneling_callable`**: the exported callback is `noexcept`, catches +all exceptions into a `std::exception_ptr`, returns a default-constructed `R` +(or `void`), and the caller rethrows via **`check_failure()`** after the foreign +call returns. See `make_exception_tunneling_callable` and `make_c_callback` in +`include/psi/functionoid/function_ref.hpp`. + +## Quick start (standalone) + +```bash +cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -S . +cmake --build build +cd build/test && ctest --output-on-failure +``` + +## Embedding (sweater / host projects) + +- `functionoid.cmake` → `Psi::Functionoid` INTERFACE target +- Include: `#include ` or `function_ref.hpp` +- sweater `work_t` = `psi::functionoid::callable` + +## Traits + +`default_traits` is copyable + RTTI-off. Hot paths use move-only noexcept variants +(`copyable=na`, `moveable=nofail`, `destructor=trivial`). Optional vtable function +pointer attributes via `PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR` — see +`include/psi/functionoid/detail/vtable_attrs.hpp`. + +--- + +### History + +An alternate Boost.Function implementation (minimize template/RTTI bloat, lower +runtime overhead, policy configurability). Lineage: Boost.Function 1.43, rebased +through the Functionoid fork. For original Boost.Function docs see +https://www.boost.org/doc/libs/1_43_0/doc/html/function.html . + +Related discussion and delegate implementations are listed in the archived notes +at the bottom of this file’s git history; the design glossary (front side / back +side, empty handler, SBO policy) still applies to `callable`. diff --git a/functionoid.cmake b/functionoid.cmake new file mode 100644 index 0000000..23866ca --- /dev/null +++ b/functionoid.cmake @@ -0,0 +1,20 @@ +############################################################################# +# Psi.Functionoid — header-only callable library. +# +# Embed via include( functionoid.cmake ) from a host CMake project or standalone +# CMakeLists.txt. +############################################################################# + +set( _functionoid_include "${CMAKE_CURRENT_LIST_DIR}/include" ) + +if ( NOT TARGET PsiFunctionoid ) + add_library( PsiFunctionoid INTERFACE ) + add_library( Psi::Functionoid ALIAS PsiFunctionoid ) + + target_include_directories( PsiFunctionoid INTERFACE "${_functionoid_include}" ) + + # Boost bits functionoid headers pull (host usually supplies Boost::boost). + if ( TARGET Boost::boost ) + target_link_libraries( PsiFunctionoid INTERFACE Boost::boost ) + endif() +endif() diff --git a/include/boost/function.hpp b/include/boost/function.hpp deleted file mode 100644 index 61e731d..0000000 --- a/include/boost/function.hpp +++ /dev/null @@ -1,40 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// -/// Boost.Functionoid library -/// -/// \file function.hpp -/// ------------------ -/// -/// Copyright (c) Domagoj Saric 2010 - 2016 -/// -/// Use, modification and distribution is subject to the Boost Software -/// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at -/// http://www.boost.org/LICENSE_1_0.txt) -/// -/// For more information, see http://www.boost.org -/// -//////////////////////////////////////////////////////////////////////////////// -#ifndef BOOST_FUNCTION_BASE_HEADER -#define BOOST_FUNCTION_BASE_HEADER -//------------------------------------------------------------------------------ -#include - -#include "functionoid/functionoid.hpp" -#include "functionoid/policies.hpp" - -// Implementation note: -// BOOST_FUNCTION_TARGET_FIX is still required by tests. -// (03.11.2010.) (Domagoj Saric) -#define BOOST_FUNCTION_TARGET_FIX( x ) -//------------------------------------------------------------------------------ -namespace boost -{ -//------------------------------------------------------------------------------ - -template -using function = functionoid::callable; - -//------------------------------------------------------------------------------ -} // namespace boost -//------------------------------------------------------------------------------ -#endif // BOOST_FUNCTION_BASE_HEADER \ No newline at end of file diff --git a/include/boost/functionoid/detail/callable_base.hpp b/include/psi/functionoid/detail/callable_base.hpp similarity index 88% rename from include/boost/functionoid/detail/callable_base.hpp rename to include/psi/functionoid/detail/callable_base.hpp index 2d64605..ad33315 100644 --- a/include/boost/functionoid/detail/callable_base.hpp +++ b/include/psi/functionoid/detail/callable_base.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// /// -/// Boost.Functionoid library +/// Psi.Functionoid library /// /// \file callable_base.hpp /// ----------------------- @@ -14,18 +14,17 @@ /// For more information, see http://www.boost.org /// //////////////////////////////////////////////////////////////////////////////// -#ifndef callable_base_hpp__2785487C_DC49_4E29_96D0_E0D00C85E40A -#define callable_base_hpp__2785487C_DC49_4E29_96D0_E0D00C85E40A #pragma once //------------------------------------------------------------------------------ -#include -#include +#include +#include +#include #include #include #include #include -#include +#include #include #include @@ -34,22 +33,16 @@ //------------------------------------------------------------------------------ namespace boost { -//------------------------------------------------------------------------------ template class reference_wrapper; +} //------------------------------------------------------------------------------ -namespace functionoid +namespace psi::functionoid { //------------------------------------------------------------------------------ namespace detail { //------------------------------------------------------------------------------ -#ifdef __clang__ -# define BOOST_AUX_NO_SANITIZE __attribute__(( no_sanitize( "function" ) )) -#else -# define BOOST_AUX_NO_SANITIZE -#endif - // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r0.html Deprecating Allocator Support in std::function using dummy_allocator = std::allocator; @@ -204,20 +197,20 @@ struct manager_ptr new ( functor_ptr( out_buffer ) ) Functor( functor ); } - static void BOOST_CC_FASTCALL clone( function_buffer_base const & in_buffer, function_buffer_base & out_buffer ) noexcept + static void clone( function_buffer_base const & in_buffer, function_buffer_base & out_buffer ) noexcept { //...zzz...even with __assume MSVC still generates branching code... //assign( *functor_ptr( in_buffer ), out_buffer, dummy_allocator() ); out_buffer.obj_ptr = in_buffer.obj_ptr; } - static void BOOST_CC_FASTCALL move( function_buffer_base && in_buffer, function_buffer_base & out_buffer ) noexcept + static void move( function_buffer_base && in_buffer, function_buffer_base & out_buffer ) noexcept { clone( in_buffer, out_buffer ); destroy( in_buffer ); } - static void BOOST_CC_FASTCALL destroy( function_buffer_base & buffer ) noexcept + static void destroy( function_buffer_base & buffer ) noexcept { debug_clear( *functor_ptr( buffer ) ); } @@ -246,18 +239,18 @@ struct manager_trivial_small new ( functor_ptr( out_buffer ) ) Functor( functor ); } - static void BOOST_CC_FASTCALL clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept + static void clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept { assign( Buffer::from_base( in_buffer ), Buffer::from_base( out_buffer ), dummy_allocator{} ); } - static void BOOST_CC_FASTCALL move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept + static void move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept { clone( in_buffer, out_buffer ); destroy( in_buffer ); } - static void BOOST_CC_FASTCALL destroy( function_buffer_base & buffer ) noexcept + static void destroy( function_buffer_base & buffer ) noexcept { debug_clear( Buffer::from_base( buffer ) ); } @@ -294,7 +287,7 @@ struct manager_trivial_heap clone( in_buffer, out_buffer ); } - static void BOOST_CC_FASTCALL clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) + static void clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) { BOOST_ASSERT( ( out_buffer.trivial_heap_obj.ptr == 0 ) || ( out_buffer.trivial_heap_obj.ptr == reinterpret_cast( -1 ) ) ); BOOST_ASSERT( ( out_buffer.trivial_heap_obj.size == 0 ) || ( out_buffer.trivial_heap_obj.size == static_cast ( -1 ) ) ); @@ -305,13 +298,13 @@ struct manager_trivial_heap std::memcpy( functor_ptr( out_buffer ), functor_ptr( in_buffer ), storage_size ); } - static void BOOST_CC_FASTCALL move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept + static void move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept { out_buffer.trivial_heap_obj = in_buffer.trivial_heap_obj; debug_clear( in_buffer.trivial_heap_obj ); } - static void BOOST_CC_FASTCALL destroy( function_buffer_base & buffer ) noexcept + static void destroy( function_buffer_base & buffer ) noexcept { BOOST_ASSERT( buffer.trivial_heap_obj.ptr ); BOOST_ASSERT( buffer.trivial_heap_obj.size ); @@ -343,20 +336,20 @@ struct manager_small new ( functor_ptr( out_buffer ) ) Functor( std::forward( functor ) ); } - static void BOOST_CC_FASTCALL clone( function_buffer_base const & in_buffer, function_buffer_base & out_buffer ) noexcept( std::is_nothrow_copy_constructible_v ) + static void clone( function_buffer_base const & in_buffer, function_buffer_base & out_buffer ) noexcept( std::is_nothrow_copy_constructible_v ) { auto const & __restrict in_functor( *functor_ptr( in_buffer ) ); assign( in_functor, Buffer::from_base( out_buffer ), dummy_allocator() ); } - static void BOOST_CC_FASTCALL move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept( std::is_nothrow_move_constructible_v ) + static void move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept( std::is_nothrow_move_constructible_v ) { auto & __restrict in_functor( *functor_ptr( in_buffer ) ); assign( std::move( in_functor ), Buffer::from_base( out_buffer ), dummy_allocator{} ); destroy( in_buffer ); } - static void BOOST_CC_FASTCALL destroy( function_buffer_base & buffer ) noexcept ( std::is_nothrow_destructible_v ) + static void destroy( function_buffer_base & buffer ) noexcept ( std::is_nothrow_destructible_v ) { auto & __restrict functor( *functor_ptr( buffer ) ); functor.~Functor(); @@ -412,18 +405,18 @@ struct manager_generic #if BOOST_MSVC // Bogus heap-overflow failure w/ VS 16.10 in implicit memcpy of OriginalAllocator{ in_functor_and_allocator.allocator() } __declspec( no_sanitize_address ) #endif // BOOST_MSVC - static void BOOST_CC_FASTCALL clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) + static void clone( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) { functor_and_allocator_t const & in_functor_and_allocator{ *functor_ptr( in_buffer ) }; assign( in_functor_and_allocator.functor(), out_buffer, in_functor_and_allocator.allocator() ); } - static void BOOST_CC_FASTCALL move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept + static void move( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept { manager_trivial_heap::move( std::move( in_buffer ), out_buffer ); } - static void BOOST_CC_FASTCALL destroy( function_buffer_base & buffer ) noexcept( std::is_nothrow_destructible_v ) + static void destroy( function_buffer_base & buffer ) noexcept( std::is_nothrow_destructible_v ) { functor_and_allocator_t & __restrict in_functor_and_allocator( *functor_ptr( buffer ) ); @@ -531,17 +524,12 @@ using functor_manager = typename functor_manager_aux /// \note Clang struggles... /// https://github.com/emscripten-core/emscripten/issues/12639#issuecomment-719235628 /// (30.11.2020.) (Domagoj Saric) -#if BOOST_WORKAROUND( BOOST_MSVC, BOOST_TESTED_AT( 1900 ) ) || ( defined( __clang__ ) && ( __clang_major__ <= 7 || __clang_major__ >= 12 ) ) -#define BOOST_AUX_NOEXCEPT_PTR( condition ) -#else -#define BOOST_AUX_NOEXCEPT_PTR( condition ) noexcept( condition ) -#endif // MSVC workaround template struct invoker { template constexpr invoker( Manager const *, StoredFunctor const * ) noexcept : invoke( &invoke_impl ) {} - ReturnType (BOOST_CC_FASTCALL * const invoke)( function_buffer_base & buffer, InvokerArguments... args ) BOOST_AUX_NOEXCEPT_PTR( is_noexcept ); + ReturnType (* const invoke)( function_buffer_base & buffer, InvokerArguments... args ) noexcept( is_noexcept ) PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR; /// \note Defined here instead of within the callable template because /// of MSVC deficiencies with noexcept( expr ) function pointers (to @@ -560,7 +548,7 @@ struct invoker /// that cannot be passed in registers in case these will be used with /// noninlineable function objects. /// (07.07.2020.) (Domagoj Saric) - static ReturnType BOOST_CC_FASTCALL invoke_impl( function_buffer_base & buffer, InvokerArguments... args ) BOOST_AUX_NOEXCEPT_PTR( is_noexcept ) + static ReturnType invoke_impl( function_buffer_base & buffer, InvokerArguments... args ) noexcept( is_noexcept ) PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR { // We provide the invoker with a manager with a minimum amount of // type information (because it already knows the stored function @@ -589,13 +577,13 @@ template struct destroyer { template constexpr destroyer( Manager const * ) noexcept : destroy( &Manager::destroy ) {} - void (BOOST_CC_FASTCALL * const destroy)( function_buffer_base & __restrict buffer ) BOOST_AUX_NOEXCEPT_PTR( Level >= support_level::nofail ); + void (* const destroy)( function_buffer_base & __restrict buffer ) noexcept( Level >= support_level::nofail ) PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR; }; template <> struct destroyer { constexpr destroyer( void const * ) noexcept {} - static void BOOST_CC_FASTCALL destroy( function_buffer_base & __restrict buffer ) noexcept { debug_clear( buffer ); } + static void destroy( function_buffer_base & __restrict buffer ) noexcept { debug_clear( buffer ); } }; template <> struct destroyer { constexpr destroyer( void const * ) noexcept {} }; @@ -604,14 +592,14 @@ template struct cloner { template constexpr cloner( Manager const * ) noexcept : clone( &Manager::clone ) {} - void (BOOST_CC_FASTCALL * const clone)( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) BOOST_AUX_NOEXCEPT_PTR( Level >= support_level::nofail ); + void (* const clone)( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept( Level >= support_level::nofail ) PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR; }; template <> struct cloner { constexpr cloner( void const * ) noexcept {} template - static void BOOST_CC_FASTCALL clone( Buffer const & __restrict in_buffer, Buffer & __restrict out_buffer ) noexcept { out_buffer = in_buffer; } + static void clone( Buffer const & __restrict in_buffer, Buffer & __restrict out_buffer ) noexcept { out_buffer = in_buffer; } }; template <> struct cloner { constexpr cloner( void const * ) noexcept {} }; @@ -620,14 +608,14 @@ template struct mover { template constexpr mover( Manager const * ) noexcept : move( &Manager::move ) {} - void (BOOST_CC_FASTCALL * const move)( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) BOOST_AUX_NOEXCEPT_PTR( Level >= support_level::nofail ); + void (* const move)( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept( Level >= support_level::nofail ) PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR; }; template <> struct mover { constexpr mover( void const * ) noexcept {} template - static void BOOST_CC_FASTCALL move( Buffer && __restrict in_buffer, Buffer & __restrict out_buffer ) noexcept { cloner::clone( in_buffer, out_buffer ); } + static void move( Buffer && __restrict in_buffer, Buffer & __restrict out_buffer ) noexcept { cloner::clone( in_buffer, out_buffer ); } }; template <> struct mover { constexpr mover( void const * ) noexcept {} }; @@ -640,7 +628,7 @@ struct reflector { template constexpr reflector( std::tuple const * ) noexcept : get_typed_functor( &functor_type_info::get_typed_functor ) {} - typed_functor (BOOST_CC_FASTCALL * const get_typed_functor)( function_buffer_base const & ) noexcept; + typed_functor (* const get_typed_functor)( function_buffer_base const & ) noexcept PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR; }; template <> struct reflector { constexpr reflector( void const * ) noexcept {} }; @@ -663,47 +651,22 @@ struct empty_checker }; -#if BOOST_WORKAROUND( BOOST_MSVC, BOOST_TESTED_AT( 1903 ) ) || defined( __clang__ /*NDK r21 TODO test more versions*/ ) -/// \note Clang seems to uncoditionally 'see' invoke_impl (in the main template) as not noexcept and then errors at the -/// assignment. -/// See the above note for BOOST_AUX_NOEXCEPT_PTR for MSVC. -/// (14.10.2016.) (Domagoj Saric) +// Clang (incl. Apple) mishandles conditional-noexcept function pointers in the +// primary invoker template; the noexcept=true partial specialization uses plain +// noexcept on the vtable slot instead. template struct invoker { template constexpr invoker( Manager const *, StoredFunctor const * ) noexcept : invoke( &invoke_impl ) {} - ReturnType (BOOST_CC_FASTCALL * const invoke)( function_buffer_base & buffer, InvokerArguments... args ) noexcept; + ReturnType (* const invoke)( function_buffer_base & buffer, InvokerArguments... args ) noexcept PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR; template - static ReturnType BOOST_CC_FASTCALL invoke_impl( detail::function_buffer_base & buffer, InvokerArguments... args ) noexcept + static ReturnType invoke_impl( function_buffer_base & buffer, InvokerArguments... args ) noexcept PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR { auto & __restrict function_object( *static_cast( static_cast( FunctionObjManager::functor_ptr( buffer ) ) ) ); return function_object( std::forward( args )... ); } }; -template <> -struct destroyer -{ - template constexpr destroyer( Manager const * ) noexcept : destroy( &Manager::destroy ) {} - void (BOOST_CC_FASTCALL * const destroy)( function_buffer_base & __restrict buffer ) BOOST_AUX_NOEXCEPT_PTR( true ); // more MSVC noexcept( ) brainfarts; -}; -template <> -struct cloner -{ - template constexpr cloner( Manager const * ) noexcept : clone( &Manager::clone ) {} - void (BOOST_CC_FASTCALL * const clone)( function_buffer_base const & __restrict in_buffer, function_buffer_base & __restrict out_buffer ) noexcept; -}; -template <> -struct mover -{ - void (BOOST_CC_FASTCALL * const move)( function_buffer_base && __restrict in_buffer, function_buffer_base & __restrict out_buffer ) BOOST_AUX_NOEXCEPT_PTR( true ); // more MSVC noexcept( ) brainfarts - template constexpr mover( Manager const * ) noexcept : move( &Manager::move ) - { - static_assert( noexcept( Manager::move( std::declval(), std::declval() ) ) ); - } -}; -#undef BOOST_AUX_NOEXCEPT_PTR -#endif // conditional noexcept mess workarounds // Implementation note: @@ -802,14 +765,13 @@ class callable_base : public callable_tag { public: /// Retrieve the type of the stored function object. - core::typeinfo const & target_type() const + boost::core::typeinfo const & target_type() const { return get_vtable().get_typed_functor( this->functor_ ).functor_type_info(); } template Functor const * target() const noexcept { return const_cast( *this ).target(); } template Functor * target() noexcept - BOOST_AUX_NO_SANITIZE { static_assert( Traits::rtti, "RTTI not enabled for this callable" ); return get_vtable().get_typed_functor( this->functor_ ). template target(); @@ -1039,7 +1001,6 @@ class callable_base : public callable_tag private: // Assignment from another functionoid helpers. void assign_functionoid_direct( callable_base const & source, vtable const & /*empty_handler_vtable*/ ) noexcept( Traits::copyable >= support_level::nofail ) - BOOST_AUX_NO_SANITIZE { static_assert( Traits::copyable != support_level::na, "Callable not copyable" ); source.get_vtable().clone( source.functor_, this->functor_ ); @@ -1106,7 +1067,6 @@ class callable_base : public callable_tag template void assign_functionoid_direct( callable_base const & source, vtable const & /*empty_handler_vtable*/ ) noexcept( OtherTraits::copyable >= support_level::nofail ) - BOOST_AUX_NO_SANITIZE { static_assert( compatible_vtables() ); static_assert( Traits::sbo_size >= OtherTraits::sbo_size ); @@ -1136,15 +1096,13 @@ class callable_base : public callable_tag // It is safe to unconditionally call destroy on an empty callable as the // empty handler's vtable will correctly handle it. - void destroy() noexcept BOOST_AUX_NO_SANITIZE { get_vtable().destroy( this->functor_ ); } + void destroy() noexcept { get_vtable().destroy( this->functor_ ); } void move_to( callable_base & destination, std::true_type /* has move*/ ) const noexcept( Traits::moveable >= support_level::nofail ) - BOOST_AUX_NO_SANITIZE { get_vtable().move ( std::move( this->functor_ ), destination.functor_ ); } void move_to( callable_base & destination, std::false_type /*not has move*/ ) const noexcept( Traits::copyable >= support_level::nofail ) - BOOST_AUX_NO_SANITIZE { get_vtable().clone( std::move( this->functor_ ), destination.functor_ ); } @@ -1330,8 +1288,5 @@ void callable_base::assign //------------------------------------------------------------------------------ } // namespace detail //------------------------------------------------------------------------------ -} // namespace functionoid -//------------------------------------------------------------------------------ -} // namespace boost +} // namespace psi::functionoid //------------------------------------------------------------------------------ -#endif // callable_base_hpp diff --git a/include/boost/function_equal.hpp b/include/psi/functionoid/detail/function_equal.hpp similarity index 81% rename from include/boost/function_equal.hpp rename to include/psi/functionoid/detail/function_equal.hpp index 2d76c75..7cae97f 100644 --- a/include/boost/function_equal.hpp +++ b/include/psi/functionoid/detail/function_equal.hpp @@ -7,10 +7,9 @@ // http://www.boost.org/LICENSE_1_0.txt) // For more information, see http://www.boost.org -#ifndef BOOST_FUNCTION_EQUAL_HPP -#define BOOST_FUNCTION_EQUAL_HPP +#pragma once -namespace boost { +namespace psi::functionoid { template bool function_equal_impl(const F& f, const G& g, long) @@ -23,6 +22,4 @@ template bool function_equal(const F& f, const G& g) { return function_equal_impl(f, g, 0); } -} // end namespace boost - -#endif // BOOST_FUNCTION_EQUAL_HPP +} // namespace psi::functionoid diff --git a/include/psi/functionoid/detail/vtable_attrs.hpp b/include/psi/functionoid/detail/vtable_attrs.hpp new file mode 100644 index 0000000..dd9e7dd --- /dev/null +++ b/include/psi/functionoid/detail/vtable_attrs.hpp @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Psi.Functionoid library +/// +/// \file vtable_attrs.hpp +/// ---------------------- +/// +/// Optional compiler attributes for vtable function pointers (invoke + manager +/// slots). Override the macros below before including functionoid.hpp, or +/// from a traits-specific header pulled in by the embedding project. +/// +/// Example (hot read-only invoke path): +/// +/// #define PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR [[gnu::pure]] +/// #include +/// +/// struct hot_traits : psi::functionoid::default_traits { +/// struct vtable_fn_attrs { +/// static constexpr bool invoke_pure = true; +/// }; +/// }; +/// +//////////////////////////////////////////////////////////////////////////////// +#pragma once + +#ifndef PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR +#define PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR +#endif + +#ifndef PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR +#define PSI_FUNCTIONOID_DETAIL_MANAGER_FN_ATTR +#endif + +//------------------------------------------------------------------------------ +namespace psi::functionoid +{ +//------------------------------------------------------------------------------ +namespace detail +{ +//------------------------------------------------------------------------------ + +/// Traits hook: `Traits::vtable_fn_attrs` may set `invoke_pure` / +/// `manager_nofail` for documentation and static introspection. Attribute +/// application itself is via the macros above (C++ cannot attach arbitrary +/// attribute tokens from a traits template today). +template +struct vtable_attr_meta +{ + using attrs = typename Traits::vtable_fn_attrs; + + static constexpr bool invoke_pure = attrs::invoke_pure; + static constexpr bool manager_nofail = attrs::manager_nofail; +}; + +//------------------------------------------------------------------------------ +} // namespace detail +//------------------------------------------------------------------------------ +} // namespace psi::functionoid +//------------------------------------------------------------------------------ diff --git a/include/psi/functionoid/function_ref.hpp b/include/psi/functionoid/function_ref.hpp new file mode 100644 index 0000000..e2ec500 --- /dev/null +++ b/include/psi/functionoid/function_ref.hpp @@ -0,0 +1,138 @@ +#pragma once +//////////////////////////////////////////////////////////////////////////////// +/// \file function_ref.hpp +/// Non-owning type-erased callable — Psi.Functionoid's `std::function_ref` slot. +/// +/// \b Optimization: trivial callables that fit in a single pointer are stored +/// inline in the ref's data word (no indirection). Larger or non-trivial targets +/// are invoked through a pointer to the caller's object; lifetime stays external. +/// +/// \b Exception tunneling: C APIs, V8, and other embedder callbacks require +/// `noexcept` function pointers and cannot propagate C++ exceptions. When the +/// source callable may throw, `make_c_callback` wraps it in +/// `exception_tunneling_callable`: the exported thunk is `noexcept`, catches +/// into `std::exception_ptr`, returns a default `R` (or `void`), and the caller +/// calls `check_failure()` after the foreign API returns to rethrow. See +/// `make_exception_tunneling_callable` below. +//////////////////////////////////////////////////////////////////////////////// +#include + +#include +#include +#include +//------------------------------------------------------------------------------ +namespace psi::functionoid { +//------------------------------------------------------------------------------ + +template +class function_ref; + +template +class [[ clang::trivial_abi ]] function_ref +{ +public: + constexpr function_ref() = default; + + template + function_ref( F && callable [[ clang::lifetimebound ]] ) noexcept + requires ( noexcept( callable( std::declval()... ) ) >= ne ) + { + auto const cb{ make_c_callback( std::forward( callable ) ) }; + data_ = cb.first; + function_ = static_cast( cb.second ); + } + + template + [[ gnu::always_inline ]] + decltype( auto ) operator()( CallArgs &&... args ) const noexcept( ne ) + { + BOOST_ASSERT_MSG( function_, "function_ref called with null function" ); + return function_( data_, std::forward( args )... ); + } + + explicit operator bool() const noexcept { return function_ != nullptr; } + + /// Wraps a potentially-throwing callable for export through a `noexcept` + /// C-style or embedder callback. Exceptions are captured in \c exception; + /// call \c check_failure() after the foreign API returns to rethrow. + template + struct exception_tunneling_callable + { + mutable Target target; + mutable std::exception_ptr exception{}; + + template + [[ gnu::always_inline ]] + decltype( auto ) operator()( CallArgs &&... args ) const noexcept + { + try { return target( std::forward( args )... ); } + catch ( ... ) { + exception = std::current_exception(); + if constexpr ( !std::is_same_v ) { + return R{}; + } + } + } + + void check_failure() const + { + if ( exception ) [[ unlikely ]] { + std::rethrow_exception( exception ); + } + } + }; // struct exception_tunneling_callable + + template + static auto make_exception_tunneling_callable( F && f [[ clang::lifetimebound ]] ) noexcept + { + using Callable = std::remove_reference_t; + + return exception_tunneling_callable< + std::conditional_t< + noexcept( auto{ std::forward( f ) } ) && ( sizeof( f ) < 4 * sizeof( void * ) ), + Callable, F + > + >{ std::forward( f ) }; + } + + /// Builds the `(data, noexcept thunk)` pair stored in this ref. Trivial + /// callables that fit in \c data_ are placement-new'd inline; otherwise the + /// thunk dereferences a pointer to the caller's object. Throwing callables + /// when \c ne is true are routed through \c exception_tunneling_callable. + template + static auto make_c_callback( F && callable [[ clang::lifetimebound ]] ) noexcept + { + using Callable = std::remove_reference_t; + if constexpr ( noexcept( callable( std::declval()... ) ) >= ne ) { + if constexpr ( std::is_trivially_copy_constructible_v && ( sizeof( Callable ) <= sizeof( data_ ) ) ) { + void * data; + new ( &data ) Callable{ callable }; + return std::pair{ + data, + []( void * f, Args... args ) noexcept( ne ) -> R { + return reinterpret_cast( f )( std::forward( args )... ); + } + }; + } else { + return std::pair{ + const_cast( static_cast( std::addressof( callable ) ) ), + []( void * const pF, Args... args ) noexcept( ne ) -> R { + return ( *static_cast( pF ) )( std::forward( args )... ); + } + }; + } + } else { + return make_c_callback( + make_exception_tunneling_callable( std::forward( callable ) ) + ); + } + } + +private: + R ( *function_ )( void *, Args... ) noexcept( ne ){}; + void * data_{}; +}; // class function_ref + +//------------------------------------------------------------------------------ +} // namespace psi::functionoid +//------------------------------------------------------------------------------ diff --git a/include/boost/functionoid/functionoid.hpp b/include/psi/functionoid/functionoid.hpp similarity index 97% rename from include/boost/functionoid/functionoid.hpp rename to include/psi/functionoid/functionoid.hpp index 1000676..bbad08a 100644 --- a/include/boost/functionoid/functionoid.hpp +++ b/include/psi/functionoid/functionoid.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// /// -/// Boost.Functionoid library +/// Psi.Functionoid library /// /// \file functionoid.hpp /// --------------------- @@ -25,10 +25,7 @@ #include #include //------------------------------------------------------------------------------ -namespace boost -{ -//------------------------------------------------------------------------------ -namespace functionoid +namespace psi::functionoid { //------------------------------------------------------------------------------ @@ -143,7 +140,7 @@ class callable : function_base( static_cast( f ), empty_handler_vtable() ) {} template - result_type BOOST_CC_FASTCALL operator()( CallArguments &&... args ) const noexcept( Traits::is_noexcept ) + result_type operator()( CallArguments &&... args ) const noexcept( Traits::is_noexcept ) { return vtable().invoke( this->functor(), std::forward< CallArguments >( args )... ); } @@ -321,7 +318,5 @@ template void operator==( callable void operator!=( callable const &, callable const & ); //------------------------------------------------------------------------------ -} // namespace functionoid -//------------------------------------------------------------------------------ -} // namespace boost +} // namespace psi::functionoid //------------------------------------------------------------------------------ diff --git a/include/boost/functionoid/functionoid_fwd.hpp b/include/psi/functionoid/functionoid_fwd.hpp similarity index 76% rename from include/boost/functionoid/functionoid_fwd.hpp rename to include/psi/functionoid/functionoid_fwd.hpp index 34cc0a4..e4f9a02 100644 --- a/include/boost/functionoid/functionoid_fwd.hpp +++ b/include/psi/functionoid/functionoid_fwd.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// /// -/// Boost.Functionoid library +/// Psi.Functionoid library /// /// \file functionoid_fwd.hpp /// ------------------------- @@ -15,13 +15,9 @@ /// For more information, see http://www.boost.org /// //////////////////////////////////////////////////////////////////////////////// -#ifndef BOOST_FUNCTIONOID_FWD_HPP -#define BOOST_FUNCTIONOID_FWD_HPP +#pragma once //------------------------------------------------------------------------------ -namespace boost -{ -//------------------------------------------------------------------------------ -namespace functionoid +namespace psi::functionoid { //------------------------------------------------------------------------------ @@ -43,8 +39,5 @@ template void swap( callable & f1, callable & f2 ) { f1.swap( f2 ); } //------------------------------------------------------------------------------ -} // namespace functionoid -//------------------------------------------------------------------------------ -} // namespace boost -//------------------------------------------------------------------------------ -#endif // BOOST_FUNCTIONOID_FWD_HPP \ No newline at end of file +} // namespace psi::functionoid +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/include/boost/functionoid/policies.hpp b/include/psi/functionoid/policies.hpp similarity index 82% rename from include/boost/functionoid/policies.hpp rename to include/psi/functionoid/policies.hpp index d46aaab..110630d 100644 --- a/include/boost/functionoid/policies.hpp +++ b/include/psi/functionoid/policies.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// /// -/// Boost.Functionoid library +/// Psi.Functionoid library /// /// \file policies.hpp /// ------------------ @@ -24,20 +24,17 @@ #include #include //------------------------------------------------------------------------------ -namespace boost -{ -//------------------------------------------------------------------------------ -namespace functionoid +namespace psi::functionoid { //------------------------------------------------------------------------------ /// The bad_function_call exception class is thrown when an empty -/// boost::functionoid object is invoked that uses the throw_on_empty empty +/// Psi.Functionoid callable is invoked that uses the throw_on_empty empty /// handler. class bad_function_call : public std::runtime_error { public: - bad_function_call() : std::runtime_error( "call to empty boost::functionoid" ) {} + bad_function_call() : std::runtime_error( "call to empty Psi.Functionoid callable" ) {} }; class throw_on_empty @@ -108,10 +105,19 @@ struct default_traits : std_traits static constexpr auto dll_safe_empty_check = false; using empty_handler = assert_on_empty; + + /// Optional hook for decorating vtable invoke/manager function pointers with + /// compiler attributes (`gnu::pure`, `clang::preserve_most`, …). Set + /// `PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR` / `MANAGER_FN_ATTR` before + /// including functionoid headers, or specialize the constexpr flags below + /// for documentation / static introspection (`detail::vtable_attr_meta`). + struct vtable_fn_attrs + { + static constexpr bool invoke_pure = false; + static constexpr bool manager_nofail = false; + }; }; // struct default_traits //------------------------------------------------------------------------------ -} // namespace functionoid -//------------------------------------------------------------------------------ -} // namespace boost +} // namespace psi::functionoid //------------------------------------------------------------------------------ diff --git a/include/boost/functionoid/rtti.hpp b/include/psi/functionoid/rtti.hpp similarity index 86% rename from include/boost/functionoid/rtti.hpp rename to include/psi/functionoid/rtti.hpp index 4a6392f..23de875 100644 --- a/include/boost/functionoid/rtti.hpp +++ b/include/psi/functionoid/rtti.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// /// -/// Boost.Functionoid library +/// Psi.Functionoid library /// /// \file rtti.hpp /// -------------- @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include @@ -36,7 +36,9 @@ template struct is_reference_wrapper cons template struct is_reference_wrapper volatile > : std::true_type {}; template struct is_reference_wrapper const volatile> : std::true_type {}; //------------------------------------------------------------------------------ -namespace functionoid +} // namespace boost +//------------------------------------------------------------------------------ +namespace psi::functionoid { //------------------------------------------------------------------------------ @@ -57,10 +59,10 @@ class typed_functor volatile_qualified_( std::is_volatile ::value ) {} - core::typeinfo const & functor_type_info() const noexcept { return type_id_; } + boost::core::typeinfo const & functor_type_info() const noexcept { return type_id_; } template - Functor * BOOST_CC_FASTCALL target() noexcept + Functor * target() noexcept { return static_cast ( @@ -74,9 +76,9 @@ class typed_functor } private: - void * BOOST_CC_FASTCALL get_functor_if_types_match + void * get_functor_if_types_match ( - core::typeinfo const & other, + boost::core::typeinfo const & other, bool const other_const_qualified, bool const other_volatile_qualified ) const noexcept @@ -96,7 +98,7 @@ class typed_functor private: void const * const p_functor_; - core::typeinfo const & type_id_; + boost::core::typeinfo const & type_id_; // Whether the type is const-qualified. bool const const_qualified_; // Whether the type is volatile-qualified. @@ -137,11 +139,11 @@ namespace detail } public: - static typed_functor BOOST_CC_FASTCALL get_typed_functor( function_buffer_base const & buffer ) noexcept + static typed_functor get_typed_functor( function_buffer_base const & buffer ) noexcept { auto * const pFunctor ( FunctorManager::functor_ptr( const_cast( buffer ) ) ); StoredFunctor * const pStoredFunctor( static_cast( static_cast( pFunctor ) ) ); - ActualFunctor * const pActualFunctor( actual_functor_ptr( pStoredFunctor, std::integral_constant::value>(), std::is_member_pointer() ) ); + ActualFunctor * const pActualFunctor( actual_functor_ptr( pStoredFunctor, std::integral_constant::value>(), std::is_member_pointer() ) ); return typed_functor( *pActualFunctor ); } }; // functor_type_info @@ -172,21 +174,19 @@ template bool operator!=( detail::function_b template bool operator!=( Functor g, detail::function_base const & f ) { return f != g; } template -bool operator==( detail::function_base const & f, reference_wrapper const g ) +bool operator==( detail::function_base const & f, boost::reference_wrapper const g ) { auto const p_f( f. template target() ); return p_f == g.get_pointer(); } template -bool operator==( reference_wrapper const g, detail::function_base const & f ) { return f == g; } +bool operator==( boost::reference_wrapper const g, detail::function_base const & f ) { return f == g; } template -bool operator!=( detail::function_base const & f, reference_wrapper const g ) { return !( f == g ); } +bool operator!=( detail::function_base const & f, boost::reference_wrapper const g ) { return !( f == g ); } template -bool operator!=( reference_wrapper const g, detail::function_base const & f ) { return f != g; } +bool operator!=( boost::reference_wrapper const g, detail::function_base const & f ) { return f != g; } //------------------------------------------------------------------------------ -} // namespace functionoid -//------------------------------------------------------------------------------ -} // namespace boost +} // namespace psi::functionoid //------------------------------------------------------------------------------ \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..75e9375 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,14 @@ +CPMAddPackage( "gh:google/googletest@1.15.2" ) +include( GoogleTest ) + +add_executable( functionoid_smoke + functionoid_smoke.cpp + function_ref_test.cpp + callable_move_only_test.cpp + callable_invoke_test.cpp + vtable_attrs_test.cpp +) +target_link_libraries( functionoid_smoke PRIVATE GTest::gtest_main Psi::Functionoid ) + +set_target_properties( functionoid_smoke PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test" ) +add_test( NAME functionoid_smoke COMMAND functionoid_smoke ) diff --git a/test/callable_invoke_test.cpp b/test/callable_invoke_test.cpp new file mode 100644 index 0000000..d6ef0b2 --- /dev/null +++ b/test/callable_invoke_test.cpp @@ -0,0 +1,29 @@ +#include + +#include + +namespace { + +int g_sum{ 0 }; + +} // namespace + +TEST( CallableInvoke, StatelessLambda ) +{ + psi::functionoid::callable fn{ []() noexcept -> int { return 42; } }; + EXPECT_EQ( fn(), 42 ); +} + +TEST( CallableInvoke, CapturingLambda ) +{ + psi::functionoid::callable fn{ [&]() noexcept { g_sum += 10; } }; + fn(); + EXPECT_EQ( g_sum, 10 ); +} + +TEST( CallableInvoke, Reassign ) +{ + psi::functionoid::callable fn{ []() noexcept -> int { return 1; } }; + fn = []() noexcept -> int { return 2; }; + EXPECT_EQ( fn(), 2 ); +} diff --git a/test/callable_move_only_test.cpp b/test/callable_move_only_test.cpp new file mode 100644 index 0000000..c3f45b6 --- /dev/null +++ b/test/callable_move_only_test.cpp @@ -0,0 +1,53 @@ +#include + +#include + +#include + +namespace { + +struct move_only_traits : psi::functionoid::default_traits +{ + static constexpr auto copyable = psi::functionoid::support_level::na; + static constexpr auto moveable = psi::functionoid::support_level::nofail; + static constexpr auto destructor = psi::functionoid::support_level::trivial; + static constexpr auto is_noexcept = true; +}; + +using move_only_void = psi::functionoid::callable; + +struct Counter +{ + int value{ 0 }; + void operator()() noexcept { ++value; } +}; + +} // namespace + +TEST( CallableMoveOnly, MoveConstructAndInvoke ) +{ + Counter c; + move_only_void fn{ [&c]() noexcept { c(); } }; + fn(); + EXPECT_EQ( c.value, 1 ); + + move_only_void moved{ std::move( fn ) }; + moved(); + EXPECT_EQ( c.value, 2 ); +} + +TEST( CallableMoveOnly, AssignFromStatelessLambda ) +{ + int hits{ 0 }; + move_only_void fn; + fn = [&]() noexcept { ++hits; }; + fn(); + EXPECT_EQ( hits, 1 ); +} + +TEST( CallableMoveOnly, EmptyNopHandler ) +{ + move_only_void fn; + // assert_on_empty is default; empty invoke is UB in release — just check empty state. + SUCCEED(); +} diff --git a/test/function_ref_test.cpp b/test/function_ref_test.cpp new file mode 100644 index 0000000..07880f7 --- /dev/null +++ b/test/function_ref_test.cpp @@ -0,0 +1,36 @@ +#include + +#include + +namespace { + +int g_value{ 0 }; + +} // namespace + +TEST( FunctionRefTest, InvokesBoundLambda ) +{ + psi::functionoid::function_ref ref{ [] { g_value = 42; } }; + ASSERT_TRUE( ref ); + ref(); + EXPECT_EQ( g_value, 42 ); +} + +TEST( FunctionRefTest, NoexceptIntReturn ) +{ + psi::functionoid::function_ref ref{ []() noexcept -> int { return 7; } }; + EXPECT_EQ( ref(), 7 ); +} + +TEST( FunctionRefTest, EmptyIsFalse ) +{ + psi::functionoid::function_ref ref; + EXPECT_FALSE( ref ); +} + +TEST( FunctionRefTest, BoostAliasMatchesPsi ) +{ + psi::functionoid::function_ref ref{ [] { ++g_value; } }; + ref(); + EXPECT_EQ( g_value, 43 ); +} diff --git a/test/functionoid_smoke.cpp b/test/functionoid_smoke.cpp new file mode 100644 index 0000000..9c5739c --- /dev/null +++ b/test/functionoid_smoke.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include + +namespace { + +struct move_only_noexcept_traits : psi::functionoid::default_traits +{ + static constexpr auto copyable = psi::functionoid::support_level::na; + static constexpr auto moveable = psi::functionoid::support_level::nofail; + static constexpr auto destructor = psi::functionoid::support_level::trivial; + static constexpr auto is_noexcept = true; +}; + +using work_t = psi::functionoid::callable; + +} // namespace + +TEST( FunctionoidSmoke, MoveOnlyNoexceptCallable ) +{ + int count{ 0 }; + work_t fn{ [&] { ++count; } }; + fn(); + EXPECT_EQ( count, 1 ); + + work_t moved{ std::move( fn ) }; + moved(); + EXPECT_EQ( count, 2 ); +} + +TEST( FunctionoidSmoke, SboStatelessLambda ) +{ + work_t fn{ []() noexcept {} }; + EXPECT_FALSE( work_t::requires_allocation ); + fn(); +} diff --git a/test/vtable_attrs_test.cpp b/test/vtable_attrs_test.cpp new file mode 100644 index 0000000..018fed7 --- /dev/null +++ b/test/vtable_attrs_test.cpp @@ -0,0 +1,37 @@ +// Compile-time test: vtable attribute macros + traits metadata hook. +#if defined( __GNUC__ ) && !defined( __clang__ ) +# define PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR __attribute__( ( pure ) ) +#elif defined( __clang__ ) +# define PSI_FUNCTIONOID_DETAIL_INVOKE_FN_ATTR [[gnu::pure]] +#endif + +#include +#include + +#include + +namespace { + +struct attributed_traits : psi::functionoid::default_traits +{ + struct vtable_fn_attrs + { + static constexpr bool invoke_pure = true; + static constexpr bool manager_nofail = false; + }; +}; + +} // namespace + +TEST( VtableAttrs, MetaFlags ) +{ + static_assert( psi::functionoid::detail::vtable_attr_meta::invoke_pure ); + static_assert( !psi::functionoid::detail::vtable_attr_meta::manager_nofail ); + static_assert( !psi::functionoid::detail::vtable_attr_meta::invoke_pure ); +} + +TEST( VtableAttrs, CallableStillInvokes ) +{ + psi::functionoid::callable fn{ []() noexcept -> int { return 7; } }; + EXPECT_EQ( fn(), 7 ); +}