From 5d0e0abe3160a64a9015bd0e63f0f6eb0ee9aa09 Mon Sep 17 00:00:00 2001 From: Fuchsia Authors Date: Fri, 9 Sep 2022 21:06:22 +0000 Subject: [PATCH] third_party/fuchsia: Copybara import of the fit library - 59a3b03935223d21a186a59e0fe1fbc53e46aef3 [fit] Reduce unnecessary function instantiations - 9b9b190756c0bf3c40671bf576cc90fe4b50fc21 [fit] Rename fit::function template parameter - f73d8db89708db77a465d7757777cf1c64899d3f [fit] Fix null_target::ops duplicate symbol errors GitOrigin-RevId: 59a3b03935223d21a186a59e0fe1fbc53e46aef3 Change-Id: Idb97ec03e5ff3b0b8b7238af536f9c63851cabcb Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/109718 Commit-Queue: Auto-Submit Reviewed-by: Rob Mohr Pigweed-Auto-Submit: Wyatt Hepler Commit-Queue: Wyatt Hepler --- .../sdk/lib/fit/include/lib/fit/function.h | 72 +- .../fit/include/lib/fit/function_internal.h | 287 ++--- .../repo/sdk/lib/fit/test/function_tests.cc | 1039 +++++++++++++++++ 3 files changed, 1228 insertions(+), 170 deletions(-) create mode 100644 third_party/fuchsia/repo/sdk/lib/fit/test/function_tests.cc diff --git a/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function.h b/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function.h index 4c8a0f0616..ba5b50531e 100644 --- a/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function.h +++ b/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function.h @@ -13,16 +13,16 @@ namespace fit { -template +template class function_impl { - static_assert(std::is_function::value, + static_assert(std::is_function::value, "fit::function must be instantiated with a function type, such as void() or " "int(char*, bool)"); }; -template +template class callback_impl { - static_assert(std::is_function::value, + static_assert(std::is_function::value, "fit::callback must be instantiated with a function type, such as void() or " "int(char*, bool)"); }; @@ -235,7 +235,7 @@ class function_impl final // Creates a function with a target moved from another function, // leaving the other function with an empty target. - function_impl(function_impl&& other) : base(static_cast(other)) {} + function_impl(function_impl&& other) noexcept : base(static_cast(other)) {} // Destroys the function, releasing its target. ~function_impl() = default; @@ -243,7 +243,7 @@ class function_impl final // Assigns the function to an empty target. Attempting to invoke the // function will abort the program. function_impl& operator=(decltype(nullptr)) { - base::assign(nullptr); + base::assign_null(); return *this; } @@ -263,7 +263,7 @@ class function_impl final result_type>, not_self_type> operator=(Callable&& function_target) { - base::assign(std::forward(function_target)); + base::assign_callable(std::forward(function_target)); return *this; } @@ -279,10 +279,10 @@ class function_impl final delete; // Move assignment - function_impl& operator=(function_impl&& other) { + function_impl& operator=(function_impl&& other) noexcept { if (&other == this) return *this; - base::assign(static_cast(other)); + base::assign_function(static_cast(other)); return *this; } @@ -313,30 +313,30 @@ class function_impl final } }; -template -void swap(function_impl& a, - function_impl& b) { +template +void swap(function_impl& a, + function_impl& b) { a.swap(b); } -template -bool operator==(const function_impl& f, +template +bool operator==(const function_impl& f, decltype(nullptr)) { return !f; } -template +template bool operator==(decltype(nullptr), - const function_impl& f) { + const function_impl& f) { return !f; } -template -bool operator!=(const function_impl& f, +template +bool operator!=(const function_impl& f, decltype(nullptr)) { return !!f; } -template +template bool operator!=(decltype(nullptr), - const function_impl& f) { + const function_impl& f) { return !!f; } @@ -393,7 +393,7 @@ class callback_impl final // Creates a callback with a target moved from another callback, // leaving the other callback with an empty target. - callback_impl(callback_impl&& other) : base(static_cast(other)) {} + callback_impl(callback_impl&& other) noexcept : base(static_cast(other)) {} // Destroys the callback, releasing its target. ~callback_impl() = default; @@ -401,7 +401,7 @@ class callback_impl final // Assigns the callback to an empty target. Attempting to invoke the // callback will abort the program. callback_impl& operator=(decltype(nullptr)) { - base::assign(nullptr); + base::assign_null(); return *this; } @@ -418,15 +418,15 @@ class callback_impl final result_type>, not_self_type> operator=(Callable&& callback_target) { - base::assign(std::forward(callback_target)); + base::assign_callable(std::forward(callback_target)); return *this; } // Move assignment - callback_impl& operator=(callback_impl&& other) { + callback_impl& operator=(callback_impl&& other) noexcept { if (&other == this) return *this; - base::assign(static_cast(other)); + base::assign_function(static_cast(other)); return *this; } @@ -469,30 +469,30 @@ class callback_impl final } }; -template -void swap(callback_impl& a, - callback_impl& b) { +template +void swap(callback_impl& a, + callback_impl& b) { a.swap(b); } -template -bool operator==(const callback_impl& f, +template +bool operator==(const callback_impl& f, decltype(nullptr)) { return !f; } -template +template bool operator==(decltype(nullptr), - const callback_impl& f) { + const callback_impl& f) { return !f; } -template -bool operator!=(const callback_impl& f, +template +bool operator!=(const callback_impl& f, decltype(nullptr)) { return !!f; } -template +template bool operator!=(decltype(nullptr), - const callback_impl& f) { + const callback_impl& f) { return !!f; } diff --git a/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function_internal.h b/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function_internal.h index 539c8a0094..40275edd8e 100644 --- a/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function_internal.h +++ b/third_party/fuchsia/repo/sdk/lib/fit/include/lib/fit/function_internal.h @@ -80,22 +80,31 @@ inline const void* unshared_target_type_id(void* /*bits*/, const void* impl_ops) // All function_base instantiations, regardless of callable type, use the same // vtable for nullptr functions. This avoids generating unnecessary identical // vtables, which reduces code size. +// +// The null_target class does not need to be a template. However, if it was not +// a template, the ops variable would need to be defined in a .cc file for C++14 +// compatibility. In C++17, null_target::ops could be defined in the class or +// elsewhere in the header as an inline variable. +template struct null_target { static void invoke(void* /*bits*/) { PW_ASSERT(false); } static const target_ops ops; + + static_assert(std::is_same::value, "Only instantiate null_target with void"); }; template struct target final - : public null_target {}; + : public null_target<> {}; inline void* null_target_get(void* /*bits*/) { return nullptr; } inline void null_target_move(void* /*from_bits*/, void* /*to_bits*/) {} -constexpr target_ops null_target::ops = {&unshared_target_type_id, &null_target_get, - &null_target_move, &trivial_target_destroy, - &null_target::invoke}; +template +constexpr target_ops null_target::ops = {&unshared_target_type_id, &null_target_get, + &null_target_move, &trivial_target_destroy, + &null_target::invoke}; // vtable for inline target function @@ -241,13 +250,14 @@ constexpr target_ops target::ops = { &target::target_type_id, &target::get, &target::move, &target::destroy, &target::invoke}; -template -class function_base; - -// Function implementation details. +// Function implementation details shared by all functions, regardless of +// signature. This class is aligned as max_align_t so that the target storage +// (bits_, the first class member) has maximum alignment. +// // See |fit::function| and |fit::callback| documentation for more information. -template -class function_base { +template +class alignas(max_align_t) generic_function_base { + public: // The inline target size must be a non-zero multiple of sizeof(void*). Uses // of |fit::function_impl| and |fit::callback_impl| may call // fit::internal::RoundUpToWord to round to a valid inline size. @@ -267,36 +277,118 @@ class function_base { static_assert(inline_target_size % sizeof(void*) == 0, "The inline target size must be a multiple of the word size"); - struct Empty {}; + // Deleted copy constructor and assign. |generic_function_base| + // implementations are move-only. + generic_function_base(const generic_function_base& other) = delete; + generic_function_base& operator=(const generic_function_base& other) = delete; + + // Move assignment must be provided by subclasses. + generic_function_base& operator=(generic_function_base&& other) = delete; + + protected: + constexpr generic_function_base() : null_bits_(), ops_(&null_target<>::ops) {} + + generic_function_base(generic_function_base&& other) noexcept { move_target_from(other); } + + ~generic_function_base() { destroy_target(); } + + // Returns true if the function has a non-empty target. + explicit operator bool() const { return ops_->get(bits_) != nullptr; } + + // Used by derived "impl" classes to implement operator=(). + // Assigns an empty target. + void assign_null() { + destroy_target(); + initialize_null_target(); + } + + // Used by derived "impl" classes to implement operator=(). + // Assigns the function with a target moved from another function, + // leaving the other function with an empty target. + void assign_function(generic_function_base&& other) { + destroy_target(); + move_target_from(other); + } + + void swap(generic_function_base& other) { + if (&other == this) + return; + + const base_target_ops* temp_ops = ops_; + // temp_bits, which stores the target, must maintain the expected alignment. + alignas(max_align_t) uint8_t temp_bits[inline_target_size]; + ops_->move(bits_, temp_bits); - struct alignas(max_align_t) storage_type { - union { - // Function context data, placed first in the struct since is has a more - // strict alignment requirement than ops. - mutable uint8_t bits[inline_target_size]; + ops_ = other.ops_; + other.ops_->move(other.bits_, bits_); - // Empty struct used when initializing the storage in the constexpr - // constructor. - Empty null_bits; - }; + other.ops_ = temp_ops; + temp_ops->move(temp_bits, other.bits_); + } - // The target_ops pointer for this function. This field has lower alignment - // requirement than bits, so placing ops after bits allows for better - // packing reducing the padding needed in some cases. - const base_target_ops* ops; + // returns an opaque ID unique to the |Callable| type of the target. + // Used by check_target_type. + const void* target_type_id() const { return ops_->target_type_id(bits_, ops_); } + + // leaves target uninitialized + void destroy_target() { ops_->destroy(bits_); } + + // assumes target is uninitialized + void initialize_null_target() { ops_ = &null_target<>::ops; } + + // Gets a pointer to the function context. + void* get() const { return ops_->get(bits_); } + + // Allow function_base to directly access bits_ and ops_ when needed. + void* bits() const { return bits_; } + const base_target_ops* ops() const { return ops_; } + void set_ops(const base_target_ops* new_ops) { ops_ = new_ops; } + + private: + // Implements the move operation, used by move construction and move + // assignment. Leaves other target initialized to null. + void move_target_from(generic_function_base& other) { + ops_ = other.ops_; + other.ops_->move(other.bits_, bits_); + other.initialize_null_target(); + } + + struct empty {}; + + union { + // Function context data. The bits_ field requires max_align_t alignment, + // but adding the alignas() at the field declaration increases the padding. + // Instead, the generic_function_base class is aligned as max_align_t, and + // bits_ is placed first in the class. Thus, bits_ MUST remain first in the + // class to ensure proper alignment. + mutable uint8_t bits_[inline_target_size]; + + // Empty struct used when initializing the storage in the constexpr + // constructor. + empty null_bits_; }; - // bits field should have a max_align_t alignment, but adding the alignas() - // at the field declaration increases the padding. Make sure the alignment is - // correct nevertheless. - static_assert(offsetof(storage_type, bits) % alignof(max_align_t) == 0, - "bits must be aligned as max_align_t"); + // The target_ops pointer for this function. This field has lower alignment + // requirement than bits, so placing ops after bits allows for better + // packing reducing the padding needed in some cases. + const base_target_ops* ops_; +}; - // Check that there's no unexpected extra padding. - static_assert(sizeof(storage_type) == - RoundUpToMultiple(inline_target_size + sizeof(storage_type::ops), - alignof(max_align_t)), - "storage_type is not minimal in size"); +template +class function_base; + +// Function implementation details that require the function signature. +// See |fit::function| and |fit::callback| documentation for more information. +template +class function_base + : public generic_function_base { + using base = generic_function_base; + + // Check alignment and size of the base, which holds the bits_ and ops_ members. + static_assert(alignof(base) == alignof(max_align_t), "Must be aligned as max_align_t"); + static_assert(sizeof(base) == RoundUpToMultiple(inline_target_size + sizeof(base_target_ops*), + alignof(max_align_t)), + "generic_function_base has unexpected padding and is not minimal in size"); template using target_type = target { using shared_target_type = target; - using null_target_type = target_type; using ops_type = const target_ops*; - public: - // Deleted copy constructor and assign. |function_base| implementations are - // move-only. - function_base(const function_base& other) = delete; - function_base& operator=(const function_base& other) = delete; - - // Move assignment must be provided by subclasses. - function_base& operator=(function_base&& other) = delete; - protected: using result_type = Result; - constexpr function_base() : storage_({.null_bits = {}, .ops = &null_target_type::ops}) {} + constexpr function_base() = default; constexpr function_base(decltype(nullptr)) : function_base() {} @@ -334,12 +416,7 @@ class function_base { initialize_target(std::forward(target)); } - function_base(function_base&& other) { move_target_from(std::move(other)); } - - ~function_base() { destroy_target(); } - - // Returns true if the function has a non-empty target. - explicit operator bool() const { return storage_.ops->get(&storage_.bits) != nullptr; } + function_base(function_base&&) noexcept = default; // Returns a pointer to the function's target. // If |check| is true (the default), the function _may_ abort if the @@ -351,7 +428,7 @@ class function_base { Callable* target(bool check = true) { if (check) check_target_type(); - return static_cast(storage_.ops->get(&storage_.bits)); + return static_cast(base::get()); } // Returns a pointer to the function's target (const version). @@ -364,22 +441,22 @@ class function_base { const Callable* target(bool check = true) const { if (check) check_target_type(); - return static_cast(storage_.ops->get(&storage_.bits)); + return static_cast(base::get()); } // Used by the derived "impl" classes to implement share(). // // The caller creates a new object of the same type as itself, and passes in // the empty object. This function first checks if |this| is already shared, - // and if not, creates a new version of itself containing a - // |std::shared_ptr| to its original self, and updates |storage_.ops| to the - // vtable for the shared version. + // and if not, creates a new version of itself containing a |std::shared_ptr| + // to its original self, and updates |ops_| to the vtable for the shared + // version. // - // Then it copies its |shared_ptr| to the |storage_.bits| of the given |copy|, - // and assigns the same shared pointer vtable to the copy's |storage_.ops|. + // Then it copies its |shared_ptr| to the |bits_| of the given |copy|, and + // assigns the same shared pointer vtable to the copy's |ops_|. // - // The target itself is not copied; it is moved to the heap and its - // lifetime is extended until all references have been released. + // The target itself is not copied; it is moved to the heap and its lifetime + // is extended until all references have been released. // // Note: This method is not supported on |fit::inline_function<>| // because it may incur a heap allocation which is contrary to @@ -387,9 +464,12 @@ class function_base { template void share_with(SharedFunction& copy) { static_assert(!require_inline, "Inline functions cannot be shared."); - if (storage_.ops->get(&storage_.bits) != nullptr) { - if (storage_.ops != &shared_target_type::ops) { - convert_to_shared_target(); + if (base::get() != nullptr) { + // Convert to a shared function if it isn't already. + if (base::ops() != &shared_target_type::ops) { + shared_target_type::initialize( + base::bits(), std::move(*static_cast(this))); + base::set_ops(&shared_target_type::ops); } copy_shared_target_to(copy); } @@ -410,14 +490,7 @@ class function_base { // invoke function. This is permitted only because invoking a null function // is an error that immediately aborts execution. Also, the null invoke // function never attempts to access any passed arguments. - return static_cast(storage_.ops)->invoke(&storage_.bits, std::forward(args)...); - } - - // Used by derived "impl" classes to implement operator=(). - // Assigns an empty target. - void assign(decltype(nullptr)) { - destroy_target(); - initialize_null_target(); + return static_cast(base::ops())->invoke(base::bits(), std::forward(args)...); } // Used by derived "impl" classes to implement operator=(). @@ -426,63 +499,23 @@ class function_base { template ()(std::declval()...)), result_type>::value>> - void assign(Callable&& target) { - destroy_target(); + void assign_callable(Callable&& target) { + base::destroy_target(); initialize_target(std::forward(target)); } - // Used by derived "impl" classes to implement operator=(). - // Assigns the function with a target moved from another function, - // leaving the other function with an empty target. - void assign(function_base&& other) { - destroy_target(); - move_target_from(std::move(other)); - } - - void swap(function_base& other) { - if (&other == this) - return; - storage_type temp_storage; - move_storage(storage_, temp_storage); - move_storage(other.storage_, storage_); - move_storage(temp_storage, other.storage_); - } - - // returns an opaque ID unique to the |Callable| type of the target. - // Used by check_target_type. - const void* target_type_id() const { - return storage_.ops->target_type_id(&storage_.bits, storage_.ops); - } - private: - // Moves the storage_type from one to another using the source's ops move() - // operation. The source storage is unnafected. - static void move_storage(const storage_type& from_storage, storage_type& to_storage) { - to_storage.ops = from_storage.ops; - from_storage.ops->move(&from_storage.bits, &to_storage.bits); - } - - // Implements the move operation, used by move construction and move - // assignment. Leaves other target initialized to null. - void move_target_from(function_base&& other) { - move_storage(other.storage_, storage_); - other.initialize_null_target(); - } - // fit::function and fit::callback are not directly copyable, but share() // will create shared references to the original object. This method // implements the copy operation for the |std::shared_ptr| wrapper. template void copy_shared_target_to(SharedFunction& copy) { copy.destroy_target(); - PW_ASSERT(storage_.ops == &shared_target_type::ops); - shared_target_type::copy_shared_ptr(&storage_.bits, ©.storage_.bits); - copy.storage_.ops = storage_.ops; + PW_ASSERT(base::ops() == &shared_target_type::ops); + shared_target_type::copy_shared_ptr(base::bits(), copy.bits()); + copy.set_ops(base::ops()); } - // assumes target is uninitialized - void initialize_null_target() { storage_.ops = &null_target_type::ops; } - // target may or may not be initialized. template void initialize_target(Callable&& target) { @@ -493,24 +526,13 @@ class function_base { static_assert(!require_inline || sizeof(DecayedCallable) <= inline_target_size, "Callable too large to store inline as requested."); if (is_null(target)) { - initialize_null_target(); + base::initialize_null_target(); } else { - storage_.ops = &target_type::ops; - target_type::initialize(&storage_.bits, std::forward(target)); + base::set_ops(&target_type::ops); + target_type::initialize(base::bits(), std::forward(target)); } } - // assumes target is uninitialized - template - void convert_to_shared_target() { - shared_target_type::initialize(&storage_.bits, - std::move(*static_cast(this))); - storage_.ops = &shared_target_type::ops; - } - - // leaves target uninitialized - void destroy_target() { storage_.ops->destroy(&storage_.bits); } - // Called by target() if |check| is true. // Checks the template parameter, usually inferred from the context of // the call to target(), and aborts the program if it can determine that @@ -518,13 +540,10 @@ class function_base { template void check_target_type() const { if (target_type::ops.target_type_id(nullptr, &target_type::ops) != - target_type_id()) { + base::target_type_id()) { PW_ASSERT(false); } } - - // The combined context data and target_ops storage. - storage_type storage_; }; } // namespace internal diff --git a/third_party/fuchsia/repo/sdk/lib/fit/test/function_tests.cc b/third_party/fuchsia/repo/sdk/lib/fit/test/function_tests.cc new file mode 100644 index 0000000000..0de6484590 --- /dev/null +++ b/third_party/fuchsia/repo/sdk/lib/fit/test/function_tests.cc @@ -0,0 +1,1039 @@ +// Copyright 2018 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "gtest/gtest.h" + +namespace { + +using Closure = void(); +using ClosureWrongReturnType = int(); +using BinaryOp = int(int a, int b); +using BinaryOpWrongReturnType = void(int a, int b); +using MoveOp = std::unique_ptr(std::unique_ptr value); +using BooleanGenerator = bool(); +using IntGenerator = int(); + +class BuildableFromInt { + public: + BuildableFromInt(int); + BuildableFromInt& operator=(int); +}; + +using BuildableFromIntGenerator = BuildableFromInt(); + +// A big object which causes a function target to be heap allocated. +struct Big { + int data[64]{}; +}; +// An object with a very large alignment requirement that cannot be placed in a +// fit::inline_function. +struct alignas(64) BigAlignment { + int data[64]{}; +}; +constexpr size_t HugeCallableSize = sizeof(Big) + sizeof(void*) * 4; + +// An object that looks like an "empty" std::function. +template +struct EmptyFunction; +template +struct EmptyFunction { + R operator()(Args... args) const { return fptr(args...); } + bool operator==(decltype(nullptr)) const { return true; } + + R(*fptr) + (Args...) = nullptr; +}; + +// An object whose state we can examine from the outside. +struct SlotMachine { + void operator()() { value++; } + int operator()(int a, int b) { + value += a * b; + return value; + } + + int value = 0; +}; + +// A move-only object which increments a counter when uniquely destroyed. +class DestructionObserver { + public: + DestructionObserver(int* counter) : counter_(counter) {} + DestructionObserver(DestructionObserver&& other) : counter_(other.counter_) { + other.counter_ = nullptr; + } + DestructionObserver(const DestructionObserver& other) = delete; + + ~DestructionObserver() { + if (counter_) + *counter_ += 1; + } + + DestructionObserver& operator=(const DestructionObserver& other) = delete; + DestructionObserver& operator=(DestructionObserver&& other) { + if (counter_) + *counter_ += 1; + counter_ = other.counter_; + other.counter_ = nullptr; + return *this; + } + + private: + int* counter_; +}; + +template +void closure() { + static_assert(fit::is_nullable::value, ""); + + // default initialization + ClosureFunction fdefault; + EXPECT_FALSE(!!fdefault); + + // nullptr initialization + ClosureFunction fnull(nullptr); + EXPECT_FALSE(!!fnull); + + // null function pointer initialization + Closure* fptr = nullptr; + ClosureFunction ffunc(fptr); + EXPECT_FALSE(!!ffunc); + + // "empty std::function" initialization + EmptyFunction empty; + ClosureFunction fwrapper(empty); + EXPECT_FALSE(!!fwrapper); + + // inline callable initialization + int finline_value = 0; + ClosureFunction finline([&finline_value] { finline_value++; }); + EXPECT_TRUE(!!finline); + finline(); + EXPECT_EQ(1, finline_value); + finline(); + EXPECT_EQ(2, finline_value); + + // heap callable initialization + int fheap_value = 0; + ClosureFunction fheap([&fheap_value, big = Big()] { fheap_value++; }); + EXPECT_TRUE(!!fheap); + fheap(); + EXPECT_EQ(1, fheap_value); + fheap(); + EXPECT_EQ(2, fheap_value); + + // move initialization of a nullptr + ClosureFunction fnull2(std::move(fnull)); + EXPECT_FALSE(!!fnull2); + + // move initialization of an inline callable + ClosureFunction finline2(std::move(finline)); + EXPECT_TRUE(!!finline2); + EXPECT_FALSE(!!finline); + finline2(); + EXPECT_EQ(3, finline_value); + finline2(); + EXPECT_EQ(4, finline_value); + + // move initialization of a heap callable + ClosureFunction fheap2(std::move(fheap)); + EXPECT_TRUE(!!fheap2); + EXPECT_FALSE(!!fheap); + fheap2(); + EXPECT_EQ(3, fheap_value); + fheap2(); + EXPECT_EQ(4, fheap_value); + + // inline mutable lambda + int fmutinline_value = 0; + ClosureFunction fmutinline([&fmutinline_value, x = 1]() mutable { + x *= 2; + fmutinline_value = x; + }); + EXPECT_TRUE(!!fmutinline); + fmutinline(); + EXPECT_EQ(2, fmutinline_value); + fmutinline(); + EXPECT_EQ(4, fmutinline_value); + + // heap-allocated mutable lambda + int fmutheap_value = 0; + ClosureFunction fmutheap([&fmutheap_value, big = Big(), x = 1]() mutable { + x *= 2; + fmutheap_value = x; + }); + EXPECT_TRUE(!!fmutheap); + fmutheap(); + EXPECT_EQ(2, fmutheap_value); + fmutheap(); + EXPECT_EQ(4, fmutheap_value); + + // move assignment of non-null + ClosureFunction fnew([] {}); + fnew = std::move(finline2); + EXPECT_TRUE(!!fnew); + fnew(); + EXPECT_EQ(5, finline_value); + fnew(); + EXPECT_EQ(6, finline_value); + + // move assignment of self + fnew = std::move(fnew); + EXPECT_TRUE(!!fnew); + fnew(); + EXPECT_EQ(7, finline_value); + + // move assignment of null + fnew = std::move(fnull); + EXPECT_FALSE(!!fnew); + + // callable assignment with operator= + int fnew_value = 0; + fnew = [&fnew_value] { fnew_value++; }; + EXPECT_TRUE(!!fnew); + fnew(); + EXPECT_EQ(1, fnew_value); + fnew(); + EXPECT_EQ(2, fnew_value); + + // nullptr assignment + fnew = nullptr; + EXPECT_FALSE(!!fnew); + + // swap (currently null) + swap(fnew, fheap2); + EXPECT_TRUE(!!fnew); + EXPECT_FALSE(!!fheap); + fnew(); + EXPECT_EQ(5, fheap_value); + fnew(); + EXPECT_EQ(6, fheap_value); + + // swap with self + swap(fnew, fnew); + EXPECT_TRUE(!!fnew); + fnew(); + EXPECT_EQ(7, fheap_value); + fnew(); + EXPECT_EQ(8, fheap_value); + + // swap with non-null + swap(fnew, fmutinline); + EXPECT_TRUE(!!fmutinline); + EXPECT_TRUE(!!fnew); + fmutinline(); + EXPECT_EQ(9, fheap_value); + fmutinline(); + EXPECT_EQ(10, fheap_value); + fnew(); + EXPECT_EQ(8, fmutinline_value); + fnew(); + EXPECT_EQ(16, fmutinline_value); + + // nullptr comparison operators + EXPECT_TRUE(fnull == nullptr); + EXPECT_FALSE(fnull != nullptr); + EXPECT_TRUE(nullptr == fnull); + EXPECT_FALSE(nullptr != fnull); + EXPECT_FALSE(fnew == nullptr); + EXPECT_TRUE(fnew != nullptr); + EXPECT_FALSE(nullptr == fnew); + EXPECT_TRUE(nullptr != fnew); + + // null function pointer assignment + fnew = fptr; + EXPECT_FALSE(!!fnew); + + // "empty std::function" assignment + fmutinline = empty; + EXPECT_FALSE(!!fmutinline); + + // target access + ClosureFunction fslot; + EXPECT_NULL(fslot.template target()); + fslot = SlotMachine{42}; + fslot(); + SlotMachine* fslottarget = fslot.template target(); + EXPECT_EQ(43, fslottarget->value); + const SlotMachine* fslottargetconst = + const_cast(fslot).template target(); + EXPECT_EQ(fslottarget, fslottargetconst); + fslot = nullptr; + EXPECT_NULL(fslot.template target()); +} + +template +void binary_op() { + static_assert(fit::is_nullable::value, ""); + + // default initialization + BinaryOpFunction fdefault; + EXPECT_FALSE(!!fdefault); + + // nullptr initialization + BinaryOpFunction fnull(nullptr); + EXPECT_FALSE(!!fnull); + + // null function pointer initialization + BinaryOp* fptr = nullptr; + BinaryOpFunction ffunc(fptr); + EXPECT_FALSE(!!ffunc); + + // "empty std::function" initialization + EmptyFunction empty; + BinaryOpFunction fwrapper(empty); + EXPECT_FALSE(!!fwrapper); + + // inline callable initialization + int finline_value = 0; + BinaryOpFunction finline([&finline_value](int a, int b) { + finline_value++; + return a + b; + }); + EXPECT_TRUE(!!finline); + EXPECT_EQ(10, finline(3, 7)); + EXPECT_EQ(1, finline_value); + EXPECT_EQ(10, finline(3, 7)); + EXPECT_EQ(2, finline_value); + + // heap callable initialization + int fheap_value = 0; + BinaryOpFunction fheap([&fheap_value, big = Big()](int a, int b) { + fheap_value++; + return a + b; + }); + EXPECT_TRUE(!!fheap); + EXPECT_EQ(10, fheap(3, 7)); + EXPECT_EQ(1, fheap_value); + EXPECT_EQ(10, fheap(3, 7)); + EXPECT_EQ(2, fheap_value); + + // move initialization of a nullptr + BinaryOpFunction fnull2(std::move(fnull)); + EXPECT_FALSE(!!fnull2); + + // move initialization of an inline callable + BinaryOpFunction finline2(std::move(finline)); + EXPECT_TRUE(!!finline2); + EXPECT_FALSE(!!finline); + EXPECT_EQ(10, finline2(3, 7)); + EXPECT_EQ(3, finline_value); + EXPECT_EQ(10, finline2(3, 7)); + EXPECT_EQ(4, finline_value); + + // move initialization of a heap callable + BinaryOpFunction fheap2(std::move(fheap)); + EXPECT_TRUE(!!fheap2); + EXPECT_FALSE(!!fheap); + EXPECT_EQ(10, fheap2(3, 7)); + EXPECT_EQ(3, fheap_value); + EXPECT_EQ(10, fheap2(3, 7)); + EXPECT_EQ(4, fheap_value); + + // inline mutable lambda + int fmutinline_value = 0; + BinaryOpFunction fmutinline([&fmutinline_value, x = 1](int a, int b) mutable { + x *= 2; + fmutinline_value = x; + return a + b; + }); + EXPECT_TRUE(!!fmutinline); + EXPECT_EQ(10, fmutinline(3, 7)); + EXPECT_EQ(2, fmutinline_value); + EXPECT_EQ(10, fmutinline(3, 7)); + EXPECT_EQ(4, fmutinline_value); + + // heap-allocated mutable lambda + int fmutheap_value = 0; + BinaryOpFunction fmutheap([&fmutheap_value, big = Big(), x = 1](int a, int b) mutable { + x *= 2; + fmutheap_value = x; + return a + b; + }); + EXPECT_TRUE(!!fmutheap); + EXPECT_EQ(10, fmutheap(3, 7)); + EXPECT_EQ(2, fmutheap_value); + EXPECT_EQ(10, fmutheap(3, 7)); + EXPECT_EQ(4, fmutheap_value); + + // move assignment of non-null + BinaryOpFunction fnew([](int a, int b) { return 0; }); + fnew = std::move(finline2); + EXPECT_TRUE(!!fnew); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(5, finline_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(6, finline_value); + + // self-assignment of non-null + fnew = std::move(fnew); + EXPECT_TRUE(!!fnew); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(7, finline_value); + + // move assignment of null + fnew = std::move(fnull); + EXPECT_FALSE(!!fnew); + + // self-assignment of non-null + fnew = std::move(fnew); + EXPECT_FALSE(!!fnew); + + // callable assignment with operator= + int fnew_value = 0; + fnew = [&fnew_value](int a, int b) { + fnew_value++; + return a + b; + }; + EXPECT_TRUE(!!fnew); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(1, fnew_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(2, fnew_value); + + // nullptr assignment + fnew = nullptr; + EXPECT_FALSE(!!fnew); + + // swap (currently null) + swap(fnew, fheap2); + EXPECT_TRUE(!!fnew); + EXPECT_FALSE(!!fheap); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(5, fheap_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(6, fheap_value); + + // swap with self + swap(fnew, fnew); + EXPECT_TRUE(!!fnew); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(7, fheap_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(8, fheap_value); + + // swap with non-null + swap(fnew, fmutinline); + EXPECT_TRUE(!!fmutinline); + EXPECT_TRUE(!!fnew); + EXPECT_EQ(10, fmutinline(3, 7)); + EXPECT_EQ(9, fheap_value); + EXPECT_EQ(10, fmutinline(3, 7)); + EXPECT_EQ(10, fheap_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(8, fmutinline_value); + EXPECT_EQ(10, fnew(3, 7)); + EXPECT_EQ(16, fmutinline_value); + + // nullptr comparison operators + EXPECT_TRUE(fnull == nullptr); + EXPECT_FALSE(fnull != nullptr); + EXPECT_TRUE(nullptr == fnull); + EXPECT_FALSE(nullptr != fnull); + EXPECT_FALSE(fnew == nullptr); + EXPECT_TRUE(fnew != nullptr); + EXPECT_FALSE(nullptr == fnew); + EXPECT_TRUE(nullptr != fnew); + + // null function pointer assignment + fnew = fptr; + EXPECT_FALSE(!!fnew); + + // "empty std::function" assignment + fmutinline = empty; + EXPECT_FALSE(!!fmutinline); + + // target access + BinaryOpFunction fslot; + EXPECT_NULL(fslot.template target()); + fslot = SlotMachine{42}; + EXPECT_EQ(54, fslot(3, 4)); + SlotMachine* fslottarget = fslot.template target(); + EXPECT_EQ(54, fslottarget->value); + const SlotMachine* fslottargetconst = + const_cast(fslot).template target(); + EXPECT_EQ(fslottarget, fslottargetconst); + fslot = nullptr; + EXPECT_NULL(fslot.template target()); +} + +TEST(FunctionTests, sized_function_size_bounds) { + auto empty = [] {}; + fit::function fempty(std::move(empty)); + static_assert(sizeof(fempty) >= sizeof(empty), "size bounds"); + + auto small = [x = 1, y = 2] { + (void)x; // suppress unused lambda capture warning + (void)y; + }; + fit::function fsmall(std::move(small)); + static_assert(sizeof(fsmall) >= sizeof(small), "size bounds"); + fsmall = [] {}; + + auto big = [big = Big(), x = 1] { (void)x; }; + fit::function fbig(std::move(big)); + static_assert(sizeof(fbig) >= sizeof(big), "size bounds"); + fbig = [x = 1, y = 2] { + (void)x; + (void)y; + }; + fbig = [] {}; + + // These statements do compile though the lambda will be copied to the heap + // when they exceed the inline size. + fempty = [x = 1, y = 2] { + (void)x; + (void)y; + }; + fsmall = [big = Big(), x = 1] { (void)x; }; + fbig = [big = Big(), x = 1, y = 2] { + (void)x; + (void)y; + }; +} + +TEST(FunctionTests, inline_function_size_bounds) { + auto empty = [] {}; + fit::inline_function fempty(std::move(empty)); + static_assert(sizeof(fempty) >= sizeof(empty), "size bounds"); + + auto small = [x = 1, y = 2] { + (void)x; // suppress unused lambda capture warning + (void)y; + }; + fit::inline_function fsmall(std::move(small)); + static_assert(sizeof(fsmall) >= sizeof(small), "size bounds"); + fsmall = [] {}; + + auto big = [big = Big(), x = 1] { (void)x; }; + fit::inline_function fbig(std::move(big)); + static_assert(sizeof(fbig) >= sizeof(big), "size bounds"); + fbig = [x = 1, y = 2] { + (void)x; + (void)y; + }; + fbig = [] {}; + +// These statements do not compile because the lambdas are too big to fit. +#if 0 + fempty = [ x = 1, y = 2 ] { + (void)x; + (void)y; + }; + fsmall = [ big = Big(), x = 1 ] { (void)x; }; + fbig = [ big = Big(), x = 1, y = 2 ] { + (void)x; + (void)y; + }; +#endif +} + +TEST(FunctionTests, inline_function_alignment_check) { +// These statements do not compile because the alignment is too large. +#if 0 + auto big = [big = BigAlignment()] { }; + fit::inline_function fbig(std::move(big)); +#endif +} + +TEST(FunctionTests, move_only_argument_and_result) { + std::unique_ptr arg(new int()); + fit::function f([](std::unique_ptr value) { + *value += 1; + return value; + }); + arg = f(std::move(arg)); + EXPECT_EQ(1, *arg); + arg = f(std::move(arg)); + EXPECT_EQ(2, *arg); +} + +void implicit_construction_helper(fit::closure closure) {} + +TEST(FunctionTests, implicit_construction) { + // ensure we can implicitly construct from nullptr + implicit_construction_helper(nullptr); + + // ensure we can implicitly construct from a lambda + implicit_construction_helper([] {}); +} + +int arg_count(fit::closure) { return 0; } +int arg_count(fit::function) { return 1; } + +TEST(FunctionTests, overload_resolution) { + EXPECT_EQ(0, arg_count([] {})); + EXPECT_EQ(1, arg_count([](int) {})); +} + +TEST(FunctionTests, sharing) { + fit::function fnull; + fit::function fnullshare1 = fnull.share(); + fit::function fnullshare2 = fnull.share(); + fit::function fnullshare3 = fnullshare1.share(); + EXPECT_FALSE(!!fnull); + EXPECT_FALSE(!!fnullshare1); + EXPECT_FALSE(!!fnullshare2); + EXPECT_FALSE(!!fnullshare3); + + int finlinevalue = 1; + int finlinedestroy = 0; + fit::function finline = [&finlinevalue, d = DestructionObserver(&finlinedestroy)] { + finlinevalue++; + }; + fit::function finlineshare1 = finline.share(); + fit::function finlineshare2 = finline.share(); + fit::function finlineshare3 = finlineshare1.share(); + EXPECT_TRUE(!!finline); + EXPECT_TRUE(!!finlineshare1); + EXPECT_TRUE(!!finlineshare2); + EXPECT_TRUE(!!finlineshare3); + finline(); + EXPECT_EQ(2, finlinevalue); + finlineshare1(); + EXPECT_EQ(3, finlinevalue); + finlineshare2(); + EXPECT_EQ(4, finlinevalue); + finlineshare3(); + EXPECT_EQ(5, finlinevalue); + finlineshare2(); + EXPECT_EQ(6, finlinevalue); + finline(); + EXPECT_EQ(7, finlinevalue); + EXPECT_EQ(0, finlinedestroy); + finline = nullptr; + EXPECT_EQ(0, finlinedestroy); + finlineshare3 = nullptr; + EXPECT_EQ(0, finlinedestroy); + finlineshare2 = nullptr; + EXPECT_EQ(0, finlinedestroy); + finlineshare1 = nullptr; + EXPECT_EQ(1, finlinedestroy); + + int fheapvalue = 1; + int fheapdestroy = 0; + fit::function fheap = [&fheapvalue, big = Big(), + d = DestructionObserver(&fheapdestroy)] { fheapvalue++; }; + fit::function fheapshare1 = fheap.share(); + fit::function fheapshare2 = fheap.share(); + fit::function fheapshare3 = fheapshare1.share(); + EXPECT_TRUE(!!fheap); + EXPECT_TRUE(!!fheapshare1); + EXPECT_TRUE(!!fheapshare2); + EXPECT_TRUE(!!fheapshare3); + fheap(); + EXPECT_EQ(2, fheapvalue); + fheapshare1(); + EXPECT_EQ(3, fheapvalue); + fheapshare2(); + EXPECT_EQ(4, fheapvalue); + fheapshare3(); + EXPECT_EQ(5, fheapvalue); + fheapshare2(); + EXPECT_EQ(6, fheapvalue); + fheap(); + EXPECT_EQ(7, fheapvalue); + EXPECT_EQ(0, fheapdestroy); + fheap = nullptr; + EXPECT_EQ(0, fheapdestroy); + fheapshare3 = nullptr; + EXPECT_EQ(0, fheapdestroy); + fheapshare2 = nullptr; + EXPECT_EQ(0, fheapdestroy); + fheapshare1 = nullptr; + EXPECT_EQ(1, fheapdestroy); + + // target access now available after share() + using ClosureFunction = fit::function; + ClosureFunction fslot = SlotMachine{42}; + fslot(); + SlotMachine* fslottarget = fslot.template target(); + EXPECT_EQ(43, fslottarget->value); + + auto shared_fslot = fslot.share(); + shared_fslot(); + fslottarget = shared_fslot.template target(); + EXPECT_EQ(44, fslottarget->value); + fslot(); + EXPECT_EQ(45, fslottarget->value); + fslot = nullptr; + EXPECT_NULL(fslot.template target()); + shared_fslot(); + EXPECT_EQ(46, fslottarget->value); + shared_fslot = nullptr; + EXPECT_NULL(shared_fslot.template target()); + +// These statements do not compile because inline functions cannot be shared +#if 0 + fit::inline_function fbad; + fbad.share(); +#endif +} + +struct Obj { + void Call() { calls++; } + + int AddOne(int x) { + calls++; + return x + 1; + } + + int Sum(int a, int b, int c) { + calls++; + return a + b + c; + } + + std::unique_ptr AddAndReturn(std::unique_ptr value) { + (*value)++; + return value; + } + + uint32_t calls = 0; +}; + +TEST(FunctionTests, deprecated_bind_member) { + Obj obj; + auto move_only_value = std::make_unique(4); + + static_assert(sizeof(fit::bind_member(&obj, &Obj::AddOne)) == 3 * sizeof(void*)); + fit::bind_member(&obj, &Obj::Call)(); + EXPECT_EQ(23, fit::bind_member(&obj, &Obj::AddOne)(22)); + EXPECT_EQ(6, fit::bind_member(&obj, &Obj::Sum)(1, 2, 3)); + move_only_value = fit::bind_member(&obj, &Obj::AddAndReturn)(std::move(move_only_value)); + EXPECT_EQ(5, *move_only_value); + EXPECT_EQ(3, obj.calls); +} + +TEST(FunctionTests, bind_member) { + Obj obj; + auto move_only_value = std::make_unique(4); + + static_assert(sizeof(fit::bind_member<&Obj::AddOne>(&obj)) == sizeof(void*)); + fit::bind_member<&Obj::Call> (&obj)(); + EXPECT_EQ(23, fit::bind_member<&Obj::AddOne>(&obj)(22)); + EXPECT_EQ(6, fit::bind_member<&Obj::Sum>(&obj)(1, 2, 3)); + move_only_value = fit::bind_member<&Obj::AddAndReturn>(&obj)(std::move(move_only_value)); + fit::function f(fit::bind_member<&Obj::Sum>(&obj)); + EXPECT_EQ(6, f(1, 2, 3)); + EXPECT_EQ(5, *move_only_value); + EXPECT_EQ(4, obj.calls); +} + +TEST(FunctionTests, callback_once) { + fit::callback cbnull; + fit::callback cbnullshare1 = cbnull.share(); + fit::callback cbnullshare2 = cbnull.share(); + fit::callback cbnullshare3 = cbnullshare1.share(); + EXPECT_FALSE(!!cbnull); + EXPECT_FALSE(!!cbnullshare1); + EXPECT_FALSE(!!cbnullshare2); + EXPECT_FALSE(!!cbnullshare3); + + int cbinlinevalue = 1; + int cbinlinedestroy = 0; + fit::callback cbinline = [&cbinlinevalue, d = DestructionObserver(&cbinlinedestroy)] { + cbinlinevalue++; + }; + EXPECT_TRUE(!!cbinline); + EXPECT_FALSE(cbinline == nullptr); + EXPECT_EQ(1, cbinlinevalue); + EXPECT_EQ(0, cbinlinedestroy); + cbinline(); // releases resources even if never shared + EXPECT_FALSE(!!cbinline); + EXPECT_TRUE(cbinline == nullptr); + EXPECT_EQ(2, cbinlinevalue); + EXPECT_EQ(1, cbinlinedestroy); + + cbinlinevalue = 1; + cbinlinedestroy = 0; + cbinline = [&cbinlinevalue, d = DestructionObserver(&cbinlinedestroy)] { cbinlinevalue++; }; + fit::callback cbinlineshare1 = cbinline.share(); + fit::callback cbinlineshare2 = cbinline.share(); + fit::callback cbinlineshare3 = cbinlineshare1.share(); + EXPECT_TRUE(!!cbinline); + EXPECT_TRUE(!!cbinlineshare1); + EXPECT_TRUE(!!cbinlineshare2); + EXPECT_TRUE(!!cbinlineshare3); + EXPECT_EQ(1, cbinlinevalue); + EXPECT_EQ(0, cbinlinedestroy); + cbinline(); + EXPECT_EQ(2, cbinlinevalue); + EXPECT_EQ(1, cbinlinedestroy); + EXPECT_FALSE(!!cbinline); + EXPECT_TRUE(cbinline == nullptr); + // cbinline(); // should abort + EXPECT_FALSE(!!cbinlineshare1); + EXPECT_TRUE(cbinlineshare1 == nullptr); + // cbinlineshare1(); // should abort + EXPECT_FALSE(!!cbinlineshare2); + // cbinlineshare2(); // should abort + EXPECT_FALSE(!!cbinlineshare3); + // cbinlineshare3(); // should abort + EXPECT_EQ(1, cbinlinedestroy); + cbinlineshare3 = nullptr; + EXPECT_EQ(1, cbinlinedestroy); + cbinline = nullptr; + EXPECT_EQ(1, cbinlinedestroy); + + int cbheapvalue = 1; + int cbheapdestroy = 0; + fit::callback cbheap = [&cbheapvalue, big = Big(), + d = DestructionObserver(&cbheapdestroy)] { cbheapvalue++; }; + EXPECT_TRUE(!!cbheap); + EXPECT_FALSE(cbheap == nullptr); + EXPECT_EQ(1, cbheapvalue); + EXPECT_EQ(0, cbheapdestroy); + cbheap(); // releases resources even if never shared + EXPECT_FALSE(!!cbheap); + EXPECT_TRUE(cbheap == nullptr); + EXPECT_EQ(2, cbheapvalue); + EXPECT_EQ(1, cbheapdestroy); + + cbheapvalue = 1; + cbheapdestroy = 0; + cbheap = [&cbheapvalue, big = Big(), d = DestructionObserver(&cbheapdestroy)] { cbheapvalue++; }; + fit::callback cbheapshare1 = cbheap.share(); + fit::callback cbheapshare2 = cbheap.share(); + fit::callback cbheapshare3 = cbheapshare1.share(); + EXPECT_TRUE(!!cbheap); + EXPECT_TRUE(!!cbheapshare1); + EXPECT_TRUE(!!cbheapshare2); + EXPECT_TRUE(!!cbheapshare3); + EXPECT_EQ(1, cbheapvalue); + EXPECT_EQ(0, cbheapdestroy); + cbheap(); + EXPECT_EQ(2, cbheapvalue); + EXPECT_EQ(1, cbheapdestroy); + EXPECT_FALSE(!!cbheap); + EXPECT_TRUE(cbheap == nullptr); + // cbheap(); // should abort + EXPECT_FALSE(!!cbheapshare1); + EXPECT_TRUE(cbheapshare1 == nullptr); + // cbheapshare1(); // should abort + EXPECT_FALSE(!!cbheapshare2); + // cbheapshare2(); // should abort + EXPECT_FALSE(!!cbheapshare3); + // cbheapshare3(); // should abort + EXPECT_EQ(1, cbheapdestroy); + cbheapshare3 = nullptr; + EXPECT_EQ(1, cbheapdestroy); + cbheap = nullptr; + EXPECT_EQ(1, cbheapdestroy); + + // Verify new design, splitting out fit::callback, still supports + // assignment of move-only "Callables" (that is, lambdas made move-only + // because they capture a move-only object, like a fit::function, for + // example!) + fit::function fn_to_wrap = []() {}; + fit::function fn_from_lambda; + fn_from_lambda = [fn = fn_to_wrap.share()]() mutable { fn(); }; + + // Same test for fit::callback + fit::callback cb_to_wrap = []() {}; + fit::callback cb_from_lambda; + cb_from_lambda = [cb = std::move(cb_to_wrap)]() mutable { cb(); }; + + // |fit::function| objects can be constructed from or assigned from + // a |fit::callback|, if the result and arguments are compatible. + fit::function fn = []() {}; + fit::callback cb = []() {}; + fit::callback cb_assign; + cb_assign = std::move(fn); + fit::callback cb_construct = std::move(fn); + fit::callback cb_share = fn.share(); + + static_assert(!std::is_convertible*, fit::callback*>::value, ""); + static_assert(!std::is_constructible, fit::callback>::value, ""); + static_assert(!std::is_assignable, fit::callback>::value, ""); + static_assert(!std::is_constructible, decltype(cb.share())>::value, ""); +#if 0 + // These statements do not compile because inline callbacks cannot be shared + fit::inline_callback cbbad; + cbbad.share(); + + { + // Attempts to copy, move, or share a callback into a fit::function<> + // should not compile. This is verified by static_assert above, and + // was verified interactively using the compiler. + fit::callback cb = []() {}; + fit::function fn = []() {}; + fit::function fn_assign; + fn_assign = cb; // BAD + fn_assign = std::move(cb); // BAD + fit::function fn_construct = cb; // BAD + fit::function fn_construct2 = std::move(cb); // BAD + fit::function fn_share = cb.share(); // BAD + } + +#endif +} + +#if defined(__cpp_constinit) +#define CONSTINIT constinit +#elif defined(__clang__) +#define CONSTINIT [[clang::require_constant_initialization]] +#else +#define CONSTINIT +#endif // __cpp_constinit + +CONSTINIT const fit::function kDefaultConstructed; +CONSTINIT const fit::function kNullptrConstructed(nullptr); + +#undef CONSTINIT + +TEST(FunctionTests, null_constructors_are_constexpr) { + EXPECT_EQ(kDefaultConstructed, nullptr); + EXPECT_EQ(kNullptrConstructed, nullptr); +} + +// Test that function inline sizes round up to the nearest word. +template +using Function = fit::function; // Use an alias for brevity + +static_assert(std::is_same, Function>::value, ""); +static_assert(std::is_same, Function>::value, ""); +static_assert(std::is_same, Function>::value, ""); +static_assert(std::is_same, Function>::value, ""); +static_assert(std::is_same, Function<2 * sizeof(void*)>>::value, ""); +static_assert(std::is_same, Function<2 * sizeof(void*)>>::value, ""); + +// Also test the inline_function, callback, and inline_callback aliases. +static_assert( + std::is_same_v, fit::inline_function>, + ""); +static_assert( + std::is_same_v, fit::inline_function>, + ""); +static_assert(std::is_same_v, fit::callback>, ""); +static_assert(std::is_same_v, fit::callback>, ""); +static_assert( + std::is_same_v, fit::inline_callback>, + ""); +static_assert( + std::is_same_v, fit::inline_callback>, + ""); + +TEST(FunctionTests, rounding_function) { + EXPECT_EQ(5, fit::internal::RoundUpToMultiple(0, 5)); + EXPECT_EQ(5, fit::internal::RoundUpToMultiple(1, 5)); + EXPECT_EQ(5, fit::internal::RoundUpToMultiple(4, 5)); + EXPECT_EQ(5, fit::internal::RoundUpToMultiple(5, 5)); + EXPECT_EQ(10, fit::internal::RoundUpToMultiple(6, 5)); + EXPECT_EQ(10, fit::internal::RoundUpToMultiple(9, 5)); + EXPECT_EQ(10, fit::internal::RoundUpToMultiple(10, 5)); +} + +// Test that the alignment of function and callback is always alignof(max_align_t). +static_assert(alignof(fit::function) == alignof(max_align_t), ""); +static_assert(alignof(fit::function) == alignof(max_align_t), ""); +static_assert(alignof(fit::function) == alignof(max_align_t), ""); +static_assert(alignof(fit::function) == alignof(max_align_t), ""); +static_assert(alignof(fit::function) == alignof(max_align_t), ""); +static_assert(alignof(fit::inline_function) == alignof(max_align_t), ""); +static_assert(alignof(fit::inline_function) == alignof(max_align_t), ""); +static_assert(alignof(fit::callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::inline_callback) == alignof(max_align_t), ""); +static_assert(alignof(fit::inline_callback) == alignof(max_align_t), ""); + +namespace test_copy_move_constructions { + +template +class assert_move_only { + static_assert(!std::is_copy_assignable::value); + static_assert(!std::is_copy_constructible::value); + static_assert(std::is_move_assignable::value); + static_assert(std::is_move_constructible::value); + + // It seems that just testing `!std::is_copy_assignable` is not enough, + // as the `fit::function` class could use a perfect-forwarding mechanism + // that still allows expressions of the form `fit::function func1 = func2` + // to compile, even though `std::is_copy_assignable` is false. + template + struct test : std::false_type {}; + template + struct test> : std::true_type {}; + + struct NoAssign { + static F v1; + static F v2; + }; + static_assert(!test::value); + + struct CanAssign { + static int v1; + static int v2; + }; + static_assert(test::value); + + template + struct test_construct : std::false_type {}; + template + struct test_construct()})>> : std::true_type {}; + + static_assert(!test_construct::value); + static_assert(test_construct::value); +}; + +template class assert_move_only>; +template class assert_move_only>; + +} // namespace test_copy_move_constructions + +} // namespace + +namespace test_conversions { +static_assert(std::is_convertible>::value, ""); +static_assert(std::is_convertible>::value, ""); +static_assert(std::is_assignable, Closure>::value, ""); +static_assert(std::is_assignable, BinaryOp>::value, ""); + +static_assert(std::is_assignable, IntGenerator>::value, ""); +static_assert(std::is_assignable, IntGenerator>::value, + ""); +static_assert(!std::is_assignable, BuildableFromIntGenerator>::value, + ""); + +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_assignable, BinaryOp>::value, ""); +static_assert(!std::is_assignable, Closure>::value, ""); + +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_assignable, ClosureWrongReturnType>::value, ""); +static_assert(!std::is_assignable, BinaryOpWrongReturnType>::value, ""); + +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_convertible>::value, ""); +static_assert(!std::is_assignable>::value, ""); +static_assert(!std::is_assignable>::value, ""); + +static_assert(std::is_same::result_type, int>::value, ""); +static_assert(std::is_same::result_type, int>::value, ""); +} // namespace test_conversions + +TEST(FunctionTests, closure_fit_function_Closure) { closure>(); } +TEST(FunctionTests, binary_op_fit_function_BinaryOp) { binary_op>(); } +TEST(FunctionTests, closure_fit_function_Closure_0u) { closure>(); } +TEST(FunctionTests, binary_op_fit_function_BinaryOp_0u) { + binary_op>(); +} +TEST(FunctionTests, closure_fit_function_Closure_HugeCallableSize) { + closure>(); +} +TEST(FunctionTests, binary_op_fit_function_BinaryOp_HugeCallableSize) { + binary_op>(); +} +TEST(FunctionTests, closure_fit_inline_function_Closure_HugeCallableSize) { + closure>(); +} +TEST(FunctionTests, binary_op_fit_inline_function_BinaryOp_HugeCallableSize) { + binary_op>(); +}