├── .github └── workflows │ └── build.yaml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── VERSION ├── include └── avakar │ └── small_function.h ├── src ├── align.h ├── impl.h ├── real_sizeof.h └── remove_member_function_pointer.h └── test └── test.cpp /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-20.04, windows-2019] 9 | cfg: [Debug, Release] 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Configure CMake 17 | run: cmake -S . -B _build -DCMAKE_BUILD_TYPE=${{ matrix.cfg }} 18 | 19 | - name: Build 20 | run: cmake --build _build --config ${{ matrix.cfg }} 21 | 22 | - name: Test 23 | working-directory: _build 24 | run: ctest -C ${{ matrix.cfg }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | _build*/ 3 | out/ 4 | CMakeSettings.json 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(avakar.small_function) 4 | 5 | if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.19") 6 | set(sources 7 | include/avakar/small_function.h 8 | src/align.h 9 | src/impl.h 10 | src/real_sizeof.h 11 | src/remove_member_function_pointer.h 12 | ) 13 | endif() 14 | add_library(avakar.small_function INTERFACE ${sources}) 15 | target_compile_features(avakar.small_function INTERFACE cxx_std_17) 16 | target_include_directories(avakar.small_function INTERFACE include) 17 | 18 | add_library(avakar::small_function ALIAS avakar.small_function) 19 | 20 | if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") 21 | include(CTest) 22 | if (BUILD_TESTING) 23 | 24 | add_executable(avakar.small_function.test 25 | test/test.cpp 26 | ) 27 | target_link_libraries(avakar.small_function.test PUBLIC avakar::small_function) 28 | target_compile_features(avakar.small_function.test PUBLIC cxx_std_20) 29 | 30 | add_test(NAME avakar.small_function.test COMMAND avakar.small_function.test) 31 | 32 | endif() 33 | endif() 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission to use, copy, modify, and/or distribute this software for any 2 | purpose with or without fee is hereby granted. 3 | 4 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 5 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 6 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 7 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 8 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 9 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 10 | PERFORMANCE OF THIS SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # small_function 2 | 3 | Allocation-free move-only alternative to `std::function` for C++17. 4 | 5 | ## Getting Started 6 | 7 | The library is header-only. To use it, clone the repo somewhere 8 | and add the `include` directory to your include path. 9 | 10 | If you're using CMake, use the FetchContent module. 11 | 12 | ```cmake 13 | FetchContent_Declare( 14 | avakar.small_function 15 | GIT_REPOSITORY https://github.com/avakar/small_function.git 16 | GIT_TAG main 17 | GIT_SHALLOW 1 18 | ) 19 | FetchContent_MakeAvailable(avakar.small_function) 20 | 21 | target_link_libraries(my_target PUBLIC avakar::small_function) 22 | ``` 23 | 24 | ## Usage 25 | 26 | Include `` and you're good to go. 27 | 28 | ```cpp 29 | #include 30 | using avakar::small_function; 31 | 32 | void get_answer(small_function fn) 33 | { 34 | if (fn) 35 | fn(42); 36 | } 37 | 38 | int main() 39 | { 40 | get_answer([](int x) { 41 | // ... 42 | }); 43 | } 44 | ``` 45 | 46 | You mustn't invoke `small_function` while it's empty. Default 47 | constructor will construct an empty object. The object 48 | contextually converts to bool indicating whether it's non-empty. 49 | 50 | By default, `small_function` objects are large enough to contain 51 | a function pointer or a lambda with at most one captured pointer. 52 | 53 | ```cpp 54 | small_function a = [this] {}; // ok, only one capture 55 | small_function b = [this, x] {}; // error, lambda too large 56 | small_function c = &abort; // ok, plain function pointer 57 | ``` 58 | 59 | You can adjust the size and alignment of the internal storage. 60 | 61 | ```cpp 62 | my_overaligned_type o; // assume sizeof(o) == 32, alignof(o) == 16 63 | small_function a = [o]{}; // error, lambda too large 64 | small_function b = [o]{}; // error, after alignment the lambda is too large 65 | small_function c = [o]{}; // ok 66 | small_function d = [o]{}; // ok, even after alignment the lambda will fit 67 | ``` 68 | 69 | Zero-sized storage is allowed. Non-capturing lambdas 70 | will fit in those, but function pointers won't. 71 | 72 | ```cpp 73 | small_function a = []{}; // ok, zero-sized lambda 74 | small_function b = [this]{}; // error, lambda too large 75 | small_function c = &abort; // error, function pointer too large 76 | ``` 77 | 78 | Template parameters can be deduced automatically from an initializer. 79 | 80 | ```cpp 81 | small_function a = [] { return 42; }; 82 | // decltype(a) == small_function 83 | 84 | small_function b = [this] { return 42; }; 85 | // decltype(b) == small_function 86 | ``` 87 | 88 | The function type can include `noexcept`. 89 | 90 | ```cpp 91 | small_function a = []{}; // error, lambda is not noexcept 92 | small_function b = []() noexcept {}; // ok 93 | small_function c = std::move(b); // ok 94 | small_function d = std::move(c); // error, weakening exception specification 95 | ``` 96 | 97 | ## Reference 98 | 99 | ```cpp 100 | template 101 | struct small_function 102 | { 103 | // Creates the `small_function` object with an empty state. 104 | // Such object will be falsy and its `operator()` must not be invoked. 105 | small_function() noexcept; 106 | 107 | // Creates the `small_function` object containing `f`. 108 | // This constructor only contributes to the overload set if 109 | // 110 | // * `f` fits in the storage (see below), 111 | // * `f(an...)` is convertible to `R`, and 112 | // * either `noexcept(f(an...))` or `!ne`. 113 | // 114 | // The resulting object will be truthy. 115 | template 116 | small_function(F f) noexcept; 117 | 118 | // Indicates whether the object is non-empty. 119 | explicit operator bool() const noexcept; 120 | 121 | // Invokes the contained function object. 122 | // Behavior is undefined if `this` is empty. 123 | R operator()(An &&... an) noexcept(ne) 124 | 125 | small_function(small_function && o) noexcept; 126 | small_function & operator=(small_function o) noexcept; 127 | }; 128 | ``` 129 | 130 | An object fits in a storage if the size of the storage is sufficient for 131 | both the object and the maximum padding for alignment. The object might 132 | have to be padded by as many as `alignof(obj) - alignof(storage)` bytes. 133 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0 2 | -------------------------------------------------------------------------------- /include/avakar/small_function.h: -------------------------------------------------------------------------------- 1 | #include "../../src/align.h" 2 | #include "../../src/impl.h" 3 | #include "../../src/remove_member_function_pointer.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace avakar { 9 | 10 | // The implementation of `small_function` is delegated to 11 | // `_small_function::impl`. We could place implementation directly 12 | // in `small_function` if we could deduce `noexcept_` from 13 | // `R(An...) noexcept(noexcept_)`, but this is not currently allowed. 14 | // 15 | // Instead, we use `split_noexcept` to decompose the function 16 | // type into `R(An...)` and `noexcept_`, which are then separately passed to 17 | // `_small_function::impl` as template arguments. 18 | // 19 | // We also do some magic with storage and alignment to eliminate waste: 20 | // * align is adjusted to be at least `alignof(_vtable_t *)`, 21 | // * size is adjusted so that there is no padding in the `small_function` object. 22 | 23 | namespace _small_function { 24 | 25 | template 26 | struct select_impl; 27 | 28 | template 29 | struct select_impl 30 | { 31 | using _vtable_ptr = vtable_t const *; 32 | static constexpr std::size_t _align = align > alignof(_vtable_ptr)? align: alignof(_vtable_ptr); 33 | static constexpr std::size_t _size = ((size + sizeof(_vtable_ptr) + _align - 1) & ~(_align - 1)) - sizeof(_vtable_ptr); 34 | 35 | using type = impl<_size, _align, noexcept_, R, An...>; 36 | }; 37 | 38 | 39 | 40 | template 41 | struct split_noexcept; 42 | 43 | template 44 | struct split_noexcept 45 | { 46 | using type = R(An...); 47 | static constexpr bool noexcept_ = false; 48 | }; 49 | 50 | template 51 | struct split_noexcept 52 | { 53 | using type = R(An...); 54 | static constexpr bool noexcept_ = true; 55 | }; 56 | 57 | 58 | 59 | template 60 | using dispatch_t 61 | = typename select_impl< 62 | typename split_noexcept::type, 63 | size, 64 | align, 65 | split_noexcept::noexcept_ 66 | >::type; 67 | 68 | } 69 | 70 | 71 | 72 | template < 73 | typename F, 74 | std::size_t size = (std::max)(sizeof(void(*)()), sizeof(void *)), 75 | std::size_t align = (std::max)(alignof(void(*)()), alignof(void *))> 76 | struct small_function 77 | : _small_function::dispatch_t 78 | { 79 | using _small_function::dispatch_t::dispatch_t; 80 | }; 81 | 82 | template 83 | small_function(R(*)(An...)) -> small_function< 84 | R(An...), 85 | sizeof(R(*)(An...)), 86 | alignof(R(*)(An...))>; 87 | 88 | template 89 | small_function(R(*)(An...) noexcept) -> small_function< 90 | R(An...) noexcept, 91 | sizeof(R(*)(An...) noexcept), 92 | alignof(R(*)(An...) noexcept)>; 93 | 94 | template 95 | small_function(F) -> small_function< 96 | _small_function::remove_member_function_pointer_t, 97 | _small_function::real_sizeof_v, 98 | alignof(F)>; 99 | 100 | } 101 | 102 | #pragma once 103 | -------------------------------------------------------------------------------- /src/align.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace avakar::_small_function { 6 | 7 | template 8 | std::enable_if_t<(from < alignof(T)), T *> realign(std::byte * p) 9 | { 10 | auto addr = reinterpret_cast(p); 11 | addr = (addr + alignof(T) - 1) & ~(alignof(T) - 1); 12 | return reinterpret_cast(addr); 13 | } 14 | 15 | template 16 | std::enable_if_t<(from >= alignof(T)), T *> realign(std::byte * p) 17 | { 18 | return reinterpret_cast(p); 19 | } 20 | 21 | } 22 | 23 | #pragma once 24 | -------------------------------------------------------------------------------- /src/impl.h: -------------------------------------------------------------------------------- 1 | #include "align.h" 2 | #include "real_sizeof.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace avakar::_small_function { 10 | 11 | template 12 | struct vtable_t 13 | { 14 | void (*destroy)(std::byte * storage) noexcept; 15 | void (*move_destroy)(std::byte * dest_storage, std::byte * src_storage) noexcept; 16 | R (*invoke)(std::byte * storage, An &&... an); 17 | }; 18 | 19 | 20 | 21 | template 22 | struct storage_t 23 | { 24 | std::byte * data() noexcept 25 | { 26 | return _bytes; 27 | } 28 | 29 | private: 30 | alignas(align) std::byte _bytes[size]; 31 | }; 32 | 33 | template 34 | struct storage_t<0, align> 35 | { 36 | std::byte * data() noexcept 37 | { 38 | return reinterpret_cast(this); 39 | } 40 | }; 41 | 42 | 43 | 44 | inline constexpr bool _fits_in_storage( 45 | std::size_t obj_size, std::size_t obj_align, 46 | std::size_t storage_size, std::size_t storage_align) 47 | { 48 | std::size_t max_pad 49 | = obj_align > storage_align 50 | ? obj_align - storage_align 51 | : 0; 52 | 53 | if (storage_size < max_pad) 54 | return false; 55 | 56 | return storage_size - max_pad >= obj_size; 57 | } 58 | 59 | template 60 | inline constexpr bool fits_in_storage_v = _fits_in_storage(obj_size, obj_align, storage_size, storage_align); 61 | 62 | 63 | 64 | 65 | template < 66 | std::size_t storage_size, 67 | std::size_t storage_align, 68 | bool noexcept_, 69 | typename R, 70 | typename... An> 71 | struct impl 72 | : private storage_t 73 | { 74 | using result_type = R; 75 | 76 | impl() noexcept 77 | : _vtable(nullptr) 78 | { 79 | } 80 | 81 | template 84 | && std::is_nothrow_move_constructible_v 85 | && (noexcept_? std::is_nothrow_invocable_r_v: std::is_invocable_r_v) 86 | && fits_in_storage_v, alignof(F), storage_size, storage_align>), int> = 0> 87 | impl(F f) noexcept 88 | { 89 | static constexpr vtable_t vtable = { 90 | /*.destroy =*/ [](std::byte * storage) noexcept { 91 | F & f = *_small_function::realign(storage); 92 | f.~F(); 93 | }, 94 | /*.move_destroy =*/ [](std::byte * dest_storage, std::byte * src_storage) noexcept { 95 | F & src = *_small_function::realign(src_storage); 96 | new(_small_function::realign(dest_storage)) F(std::move(src)); 97 | src.~F(); 98 | }, 99 | /*.invoke =*/ [](std::byte * storage, An &&... an) noexcept(noexcept_) -> R { 100 | F & f = *_small_function::realign(storage); 101 | return std::invoke(f, std::forward(an)...); 102 | }, 103 | }; 104 | 105 | _vtable = &vtable; 106 | new(_small_function::realign(this->data())) F(std::move(f)); 107 | } 108 | 109 | template && (!noexcept_ || noexcept2)), int> = 0> 111 | impl(impl && o) noexcept 112 | : _vtable(o._vtable) 113 | { 114 | o._vtable = nullptr; 115 | if (_vtable) 116 | _vtable->move_destroy(this->data(), o.data()); 117 | } 118 | 119 | explicit operator bool() const noexcept 120 | { 121 | return _vtable != nullptr; 122 | } 123 | 124 | R operator()(An... an) noexcept(noexcept_) 125 | { 126 | return _vtable->invoke(this->data(), std::forward(an)...); 127 | } 128 | 129 | ~impl() 130 | { 131 | if (_vtable) 132 | _vtable->destroy(this->data()); 133 | } 134 | 135 | impl(impl && o) noexcept 136 | : _vtable(o._vtable) 137 | { 138 | o._vtable = nullptr; 139 | if (_vtable) 140 | _vtable->move_destroy(this->data(), o.data()); 141 | } 142 | 143 | impl & operator=(impl o) noexcept 144 | { 145 | if (_vtable) 146 | _vtable->destroy(this->data()); 147 | if (o._vtable) 148 | o._vtable->move_destroy(this->data(), o.data()); 149 | _vtable = o._vtable; 150 | o._vtable = nullptr; 151 | return *this; 152 | } 153 | 154 | private: 155 | template 156 | friend struct impl; 157 | 158 | vtable_t const * _vtable; 159 | }; 160 | 161 | 162 | 163 | 164 | } 165 | 166 | #pragma once 167 | -------------------------------------------------------------------------------- /src/real_sizeof.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace avakar::_small_function { 5 | 6 | template 7 | struct real_sizeof 8 | : std::integral_constant 9 | { 10 | }; 11 | 12 | template 13 | struct real_sizeof>> 14 | : std::integral_constant 15 | { 16 | }; 17 | 18 | template 19 | inline constexpr std::size_t real_sizeof_v = real_sizeof::value; 20 | 21 | } 22 | 23 | #pragma once 24 | -------------------------------------------------------------------------------- /src/remove_member_function_pointer.h: -------------------------------------------------------------------------------- 1 | namespace avakar::_small_function { 2 | 3 | // This is a helper type function to strip the pointer-to-member portion 4 | // of pointers to member functions. This is used in template deduction guides 5 | // when `F` is the type of a function object used as a constructor argument and 6 | // `decltype(&F::operator())` exists (in particular, the overload set contains 7 | // a single member that is not a function template). 8 | // 9 | // This will usually be `R (F::*)(An...)`, which we then strip to `R(An...)`. 10 | // 11 | // In general, the operator can be inherited from one of `F`'s bases 12 | // and can have `const` and `noexcept` modifiers. 13 | // We ignore `volatile` completely as it is deprecated anyway. 14 | 15 | template 16 | struct remove_member_function_pointer; 17 | 18 | template 19 | using remove_member_function_pointer_t = typename remove_member_function_pointer::type; 20 | 21 | 22 | 23 | template 24 | struct remove_member_function_pointer 25 | { 26 | using type = R(An...); 27 | }; 28 | 29 | template 30 | struct remove_member_function_pointer 31 | { 32 | using type = R(An...); 33 | }; 34 | 35 | template 36 | struct remove_member_function_pointer 37 | { 38 | using type = R(An...) noexcept; 39 | }; 40 | 41 | template 42 | struct remove_member_function_pointer 43 | { 44 | using type = R(An...) noexcept; 45 | }; 46 | 47 | } 48 | 49 | #pragma once 50 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #ifndef PP_STRINGIFY 5 | #define PP_STRINGIFY2(x) #x 6 | #define PP_STRINGIFY(x) PP_STRINGIFY2(x) 7 | #endif 8 | struct assertion_failed 9 | : std::runtime_error 10 | { 11 | using runtime_error::runtime_error; 12 | }; 13 | #define require(cond) do { if (!(cond)) throw ::assertion_failed(__FILE__ "(" PP_STRINGIFY(__LINE__) "): assertion failed: " #cond); } while (0) 14 | 15 | using avakar::small_function; 16 | 17 | static void test_empty() 18 | { 19 | small_function fn; 20 | require(!fn); 21 | 22 | fn = [] {}; 23 | require(fn); 24 | } 25 | 26 | static void test_return() 27 | { 28 | small_function fn = [] { return 42; }; 29 | require(fn); 30 | require(fn() == 42); 31 | } 32 | 33 | void test_args() 34 | { 35 | small_function fn = [](int a) { return a; }; 36 | require(fn); 37 | require(fn(45) == 45); 38 | } 39 | 40 | void test_default_args() 41 | { 42 | small_function fn = [](int a = 44) { return a; }; 43 | require(fn); 44 | require(fn() == 44); 45 | } 46 | 47 | static void test_move_construct() 48 | { 49 | small_function fn = [] {}; 50 | require(fn); 51 | 52 | small_function fn2 = std::move(fn); 53 | require(!fn); 54 | require(fn2); 55 | } 56 | 57 | static void test_move_assign() 58 | { 59 | small_function fn = [] {}; 60 | small_function fn2; 61 | 62 | require(fn); 63 | require(!fn2); 64 | 65 | fn2 = std::move(fn); 66 | 67 | require(!fn); 68 | require(fn2); 69 | } 70 | 71 | static void test_size_conversion() 72 | { 73 | small_function fn = [] { return 46; }; 74 | require(fn() == 46); 75 | 76 | small_function fn2 = std::move(fn); 77 | require(fn2() == 46); 78 | } 79 | 80 | static void test_noexcept() 81 | { 82 | small_function fn; 83 | require(!noexcept(fn())); 84 | 85 | small_function fn2; 86 | require(noexcept(fn2())); 87 | 88 | } 89 | 90 | static void test_noexcept_convertion() 91 | { 92 | small_function fn; 93 | small_function fn2 = []() noexcept { return 42; }; 94 | require(fn2); 95 | require(fn2() == 42); 96 | 97 | fn = std::move(fn2); 98 | require(fn); 99 | require(!fn2); 100 | require(fn() == 42); 101 | } 102 | 103 | static int _noexcept_fn() noexcept 104 | { 105 | return 43; 106 | } 107 | 108 | static void test_deduction() 109 | { 110 | small_function fn = &test_deduction; 111 | static_assert(std::is_same_v>); 112 | 113 | small_function fn2 = &_noexcept_fn; 114 | static_assert(std::is_same_v>); 115 | require(fn2() == 43); 116 | 117 | small_function fn3 = [] {}; 118 | static_assert(std::is_same_v>); 119 | static_assert(std::is_convertible_v>); 120 | 121 | small_function fn4 = []() noexcept {}; 122 | static_assert(std::is_same_v>); 123 | static_assert(std::is_convertible_v>); 124 | static_assert(std::is_convertible_v>); 125 | 126 | small_function fn5 = std::move(fn2); 127 | static_assert(std::is_same_v); 128 | } 129 | 130 | static void test_default_align() 131 | { 132 | //require(std::is_same_v 133 | } 134 | 135 | struct alignas(16) _overaligned 136 | { 137 | int value; 138 | }; 139 | 140 | static void test_realign() 141 | { 142 | _overaligned v{ 50 }; 143 | small_function<_overaligned(), 24, 8> fn = [v] { return v; }; 144 | require(fn().value == 50); 145 | } 146 | 147 | static void test_pass_lvalue() 148 | { 149 | small_function fn = [](int x) { return x; }; 150 | 151 | int x = 1; 152 | require(fn(x) == 1); 153 | 154 | small_function fn2 = [](int & x) -> int & { return x; }; 155 | require(&fn2(x) == &x); 156 | } 157 | 158 | #include 159 | int main() 160 | { 161 | try 162 | { 163 | test_empty(); 164 | test_return(); 165 | test_args(); 166 | test_default_args(); 167 | test_move_construct(); 168 | test_move_assign(); 169 | test_size_conversion(); 170 | test_noexcept(); 171 | test_noexcept_convertion(); 172 | test_deduction(); 173 | test_realign(); 174 | test_pass_lvalue(); 175 | } 176 | catch (assertion_failed const & e) 177 | { 178 | std::cerr << e.what() << "\n"; 179 | return 1; 180 | } 181 | 182 | return 0; 183 | } 184 | --------------------------------------------------------------------------------