// 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 <lib/fit/function.h>
#include <lib/stdcompat/bit.h>

#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <type_traits>

#include "gtest/gtest.h"

#include "pw_polyfill/language_feature_macros.h"

namespace {

using ::std::size_t;

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<int>(std::unique_ptr<int> 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 <typename>
struct EmptyFunction;
template <typename R, typename... Args>
struct EmptyFunction<R(Args...)> {
  R operator()(Args... args) const { return fptr(args...); }
  bool operator==(decltype(nullptr)) const { return true; }

  R(*fptr)
  (Args...) = nullptr;
};

struct alignas(4 * alignof(void*)) LargeAlignedCallable {
  void operator()() { calls += 1; }

  int8_t calls = 0;
};

// 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_;
};

// Simple Allocator that only allows 1 object to be allocated/deallocated per call. All template
// instantiations share the same resources.
template <typename T>
class SingleObjectAllocator;

// Shared resources for SingleObjectAllocator template instantiations.
class SingleObjectAllocatorResources final {
 private:
  template <typename T>
  friend class SingleObjectAllocator;

  // Tuned to current tests. May need to be changed if test requirements change.
  static constexpr size_t kObjectSize{HugeCallableSize};
  static constexpr size_t kObjectAlignment{alignof(LargeAlignedCallable)};
  static constexpr size_t kMaxNumObjects{2u};

  struct Obj {
    alignas(kObjectAlignment) std::byte obj[kObjectSize];
  };

  inline static std::array<Obj, kMaxNumObjects> heap_;
  // Store `bool`s separately instead of in `Obj`, so there doesn't end up being a large amount of
  // padding from the aligned `obj` arrays in the `Obj` structs.
  inline static std::array<bool, kMaxNumObjects> in_use_{};
};

template <typename T>
class SingleObjectAllocator final {
 public:
  using value_type = T;
  using pointer = value_type*;
  using size_type = size_t;

  struct is_always_equal : public std::true_type {};

  constexpr SingleObjectAllocator() = default;
  template <typename U>
  constexpr SingleObjectAllocator(const SingleObjectAllocator<U>&) {}
  template <typename U>
  constexpr SingleObjectAllocator(SingleObjectAllocator<U>&&) {}

  pointer allocate(size_type n) {
    if (n != 1u) {
      return nullptr;
    }
    for (size_t index{0u}; index < in_use_.size(); ++index) {
      if (!in_use_.at(index)) {
        in_use_.at(index) = true;
        return reinterpret_cast<pointer>(&heap_.at(index).obj);
      }
    }
    return nullptr;
  }

  void deallocate(pointer p, size_type n) {
    ASSERT_EQ(n, 1u);
    for (size_t index{0u}; index < heap_.size(); ++index) {
      if (reinterpret_cast<pointer>(&heap_.at(index).obj) == p) {
        ASSERT_TRUE(in_use_.at(index));
        in_use_.at(index) = false;
        return;
      }
    }
    FAIL();
  }

  constexpr size_type max_size() const { return 1u; }

  template <typename U>
  constexpr bool operator==(const SingleObjectAllocator<U>&) const {
    return true;
  }
  template <typename U>
  constexpr bool operator!=(const SingleObjectAllocator<U>&) const {
    return false;
  }

 private:
  // Shared resources.
  static_assert(sizeof(value_type) <= SingleObjectAllocatorResources::kObjectSize);
  static_assert(alignof(value_type) <= SingleObjectAllocatorResources::kObjectAlignment);
  inline static auto& heap_ = SingleObjectAllocatorResources::heap_;
  inline static auto& in_use_ = SingleObjectAllocatorResources::in_use_;
  static_assert(heap_.size() == in_use_.size());
};

template <typename ClosureFunction>
void closure() {
  static_assert(fit::is_nullable<ClosureFunction>::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<Closure> 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<decltype(nullptr)>());
  fslot = SlotMachine{42};
  fslot();
  SlotMachine* fslottarget = fslot.template target<SlotMachine>();
  EXPECT_EQ(43, fslottarget->value);
  const SlotMachine* fslottargetconst =
      const_cast<const ClosureFunction&>(fslot).template target<SlotMachine>();
  EXPECT_EQ(fslottarget, fslottargetconst);
  fslot = nullptr;
  EXPECT_NULL(fslot.template target<decltype(nullptr)>());
}

template <typename BinaryOpFunction>
void binary_op() {
  static_assert(fit::is_nullable<BinaryOpFunction>::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<BinaryOp> 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<decltype(nullptr)>());
  fslot = SlotMachine{42};
  EXPECT_EQ(54, fslot(3, 4));
  SlotMachine* fslottarget = fslot.template target<SlotMachine>();
  EXPECT_EQ(54, fslottarget->value);
  const SlotMachine* fslottargetconst =
      const_cast<const BinaryOpFunction&>(fslot).template target<SlotMachine>();
  EXPECT_EQ(fslottarget, fslottargetconst);
  fslot = nullptr;
  EXPECT_NULL(fslot.template target<decltype(nullptr)>());
}

TEST(FunctionTests, sized_function_size_bounds) {
  auto empty = [] {};
  fit::function<Closure, sizeof(empty)> 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<Closure, sizeof(small)> fsmall(std::move(small));
  static_assert(sizeof(fsmall) >= sizeof(small), "size bounds");
  fsmall = [] {};

  auto big = [big = Big(), x = 1] { (void)x; };
  fit::function<Closure, sizeof(big)> 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<Closure, sizeof(empty)> 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<Closure, sizeof(small)> fsmall(std::move(small));
  static_assert(sizeof(fsmall) >= sizeof(small), "size bounds");
  fsmall = [] {};

  auto big = [big = Big(), x = 1] { (void)x; };
  fit::inline_function<Closure, sizeof(big)> 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<Closure, sizeof(big)> fbig(std::move(big));
#endif
}

TEST(FunctionTests, move_only_argument_and_result) {
  std::unique_ptr<int> arg(new int());
  fit::function<MoveOp> f([](std::unique_ptr<int> 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<void(int)>) { return 1; }

TEST(FunctionTests, overload_resolution) {
  EXPECT_EQ(0, arg_count([] {}));
  EXPECT_EQ(1, arg_count([](int) {}));
}

TEST(FunctionTests, sharing) {
  fit::function<Closure> fnull;
  fit::function<Closure> fnullshare1 = fnull.share();
  fit::function<Closure> fnullshare2 = fnull.share();
  fit::function<Closure> fnullshare3 = fnullshare1.share();
  EXPECT_FALSE(!!fnull);
  EXPECT_FALSE(!!fnullshare1);
  EXPECT_FALSE(!!fnullshare2);
  EXPECT_FALSE(!!fnullshare3);

  int finlinevalue = 1;
  int finlinedestroy = 0;
  fit::function<Closure> finline = [&finlinevalue, d = DestructionObserver(&finlinedestroy)] {
    finlinevalue++;
  };
  fit::function<Closure> finlineshare1 = finline.share();
  fit::function<Closure> finlineshare2 = finline.share();
  fit::function<Closure> 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<Closure> fheap = [&fheapvalue, big = Big(),
                                  d = DestructionObserver(&fheapdestroy)] { fheapvalue++; };
  fit::function<Closure> fheapshare1 = fheap.share();
  fit::function<Closure> fheapshare2 = fheap.share();
  fit::function<Closure> 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<Closure, HugeCallableSize>;
  ClosureFunction fslot = SlotMachine{42};
  fslot();
  SlotMachine* fslottarget = fslot.template target<SlotMachine>();
  EXPECT_EQ(43, fslottarget->value);

  auto shared_fslot = fslot.share();
  shared_fslot();
  fslottarget = shared_fslot.template target<SlotMachine>();
  EXPECT_EQ(44, fslottarget->value);
  fslot();
  EXPECT_EQ(45, fslottarget->value);
  fslot = nullptr;
  EXPECT_NULL(fslot.template target<decltype(nullptr)>());
  shared_fslot();
  EXPECT_EQ(46, fslottarget->value);
  shared_fslot = nullptr;
  EXPECT_NULL(shared_fslot.template target<decltype(nullptr)>());

// These statements do not compile because inline functions cannot be shared
#if 0
    fit::inline_function<Closure> fbad;
    fbad.share();
#endif
}

TEST(FunctionTests, sharing_with_custom_allocator) {
  int fheapvalue = 1;
  int fheapdestroy = 0;
  fit::function<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>> fheap =
      [&fheapvalue, big = Big(), d = DestructionObserver(&fheapdestroy)] { fheapvalue++; };
  fit::function<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>>
      fheapshare1 = fheap.share();
  fit::function<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>>
      fheapshare2 = fheap.share();
  fit::function<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>>
      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);
}

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<int> AddAndReturn(std::unique_ptr<int> value) {
    (*value)++;
    return value;
  }

  uint32_t calls = 0;
};

TEST(FunctionTests, deprecated_bind_member) {
  Obj obj;
  auto move_only_value = std::make_unique<int>(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<int>(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<int(int, int, int)> 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<Closure> cbnull;
  fit::callback<Closure> cbnullshare1 = cbnull.share();
  fit::callback<Closure> cbnullshare2 = cbnull.share();
  fit::callback<Closure> cbnullshare3 = cbnullshare1.share();
  EXPECT_FALSE(!!cbnull);
  EXPECT_FALSE(!!cbnullshare1);
  EXPECT_FALSE(!!cbnullshare2);
  EXPECT_FALSE(!!cbnullshare3);

  int cbinlinevalue = 1;
  int cbinlinedestroy = 0;
  fit::callback<Closure> 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<Closure> cbinlineshare1 = cbinline.share();
  fit::callback<Closure> cbinlineshare2 = cbinline.share();
  fit::callback<Closure> 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<Closure> 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<Closure> cbheapshare1 = cbheap.share();
  fit::callback<Closure> cbheapshare2 = cbheap.share();
  fit::callback<Closure> 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<void()> fn_to_wrap = []() {};
  fit::function<void()> fn_from_lambda;
  fn_from_lambda = [fn = fn_to_wrap.share()]() mutable { fn(); };

  // Same test for fit::callback
  fit::callback<void()> cb_to_wrap = []() {};
  fit::callback<void()> 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<Closure> fn = []() {};
  fit::callback<Closure> cb = []() {};
  fit::callback<Closure> cb_assign;
  cb_assign = std::move(fn);
  fit::callback<Closure> cb_construct = std::move(fn);
  fit::callback<Closure> cb_share = fn.share();

  static_assert(!std::is_convertible<fit::function<void()>*, fit::callback<void()>*>::value, "");
  static_assert(!std::is_constructible<fit::function<void()>, fit::callback<void()>>::value, "");
  static_assert(!std::is_assignable<fit::function<void()>, fit::callback<void()>>::value, "");
  static_assert(!std::is_constructible<fit::function<void()>, decltype(cb.share())>::value, "");
#if 0
    // These statements do not compile because inline callbacks cannot be shared
    fit::inline_callback<Closure> 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<Closure> cb = []() {};
        fit::function<Closure> fn = []() {};
        fit::function<Closure> fn_assign;
        fn_assign = cb;                                       // BAD
        fn_assign = std::move(cb);                            // BAD
        fit::function<Closure> fn_construct = cb;             // BAD
        fit::function<Closure> fn_construct2 = std::move(cb); // BAD
        fit::function<Closure> fn_share = cb.share();         // BAD
    }

#endif
}

TEST(FunctionTests, callback_with_custom_allocator) {
  int cbheapvalue = 1;
  int cbheapdestroy = 0;
  fit::callback<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>> cbheap =
      [&cbheapvalue, big = Big(), d = DestructionObserver(&cbheapdestroy)] { cbheapvalue++; };

  EXPECT_TRUE(!!cbheap);
  EXPECT_FALSE(cbheap == nullptr);
  EXPECT_EQ(1, cbheapvalue);
  EXPECT_EQ(0, cbheapdestroy);
  cbheap();
  EXPECT_FALSE(!!cbheap);
  EXPECT_TRUE(cbheap == nullptr);
  EXPECT_EQ(2, cbheapvalue);
  EXPECT_EQ(1, cbheapdestroy);
}

PW_CONSTINIT const fit::function<void()> kDefaultConstructed;
PW_CONSTINIT const fit::function<void()> kNullptrConstructed(nullptr);

TEST(FunctionTests, null_constructors_are_constexpr) {
  EXPECT_EQ(kDefaultConstructed, nullptr);
  EXPECT_EQ(kNullptrConstructed, nullptr);
}

TEST(FunctionTests, function_with_callable_aligned_larger_than_inline_size) {
  static_assert(sizeof(LargeAlignedCallable) > sizeof(void*), "Should not fit inline in function.");

  fit::function<void(), sizeof(void*)> function = LargeAlignedCallable();

  static_assert(alignof(LargeAlignedCallable) > alignof(decltype(function)));

  // Verify that the allocated target is aligned correctly.
  LargeAlignedCallable* callable_ptr = function.target<LargeAlignedCallable>();
  EXPECT_EQ(cpp20::bit_cast<uintptr_t>(callable_ptr) % alignof(LargeAlignedCallable), 0u);

  function();
  EXPECT_EQ(callable_ptr->calls, 1);
}

TEST(FunctionTests, function_with_callable_aligned_larger_than_inline_size_with_custom_allocator) {
  static_assert(sizeof(LargeAlignedCallable) > sizeof(void*), "Should not fit inline in function.");

  fit::function<void(), sizeof(void*), SingleObjectAllocator<std::byte>> function =
      LargeAlignedCallable();

  static_assert(alignof(LargeAlignedCallable) > alignof(decltype(function)));

  // Verify that the allocated target is aligned correctly.
  LargeAlignedCallable* callable_ptr = function.target<LargeAlignedCallable>();
  EXPECT_EQ(cpp20::bit_cast<uintptr_t>(callable_ptr) % alignof(LargeAlignedCallable), 0u);

  function();
  EXPECT_EQ(callable_ptr->calls, 1);
}

// Test that function inline sizes round up to the nearest word.
template <size_t bytes>
using Function = fit::function<void(), bytes>;  // Use an alias for brevity

static_assert(std::is_same<Function<0>, Function<sizeof(void*)>>::value, "");
static_assert(std::is_same<Function<1>, Function<sizeof(void*)>>::value, "");
static_assert(std::is_same<Function<sizeof(void*) - 1>, Function<sizeof(void*)>>::value, "");
static_assert(std::is_same<Function<sizeof(void*)>, Function<sizeof(void*)>>::value, "");
static_assert(std::is_same<Function<sizeof(void*) + 1>, Function<2 * sizeof(void*)>>::value, "");
static_assert(std::is_same<Function<2 * sizeof(void*)>, Function<2 * sizeof(void*)>>::value, "");

// Also test the inline_function, callback, and inline_callback aliases.
static_assert(
    std::is_same_v<fit::inline_function<void(), 0>, fit::inline_function<void(), sizeof(void*)>>,
    "");
static_assert(
    std::is_same_v<fit::inline_function<void(), 1>, fit::inline_function<void(), sizeof(void*)>>,
    "");
static_assert(std::is_same_v<fit::callback<void(), 0>, fit::callback<void(), sizeof(void*)>>, "");
static_assert(std::is_same_v<fit::callback<void(), 1>, fit::callback<void(), sizeof(void*)>>, "");
static_assert(
    std::is_same_v<fit::inline_callback<void(), 0>, fit::inline_callback<void(), sizeof(void*)>>,
    "");
static_assert(
    std::is_same_v<fit::inline_callback<void(), 1>, fit::inline_callback<void(), sizeof(void*)>>,
    "");

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 the minimum of alignof(max_align_t)
// and largest possible alignment for the specified inline target size.
constexpr size_t ExpectedAlignment(size_t alignment_32, size_t alignment_64) {
  if (sizeof(void*) == 4) {
    return std::min(alignment_32, alignof(max_align_t));
  }
  if (sizeof(void*) == 8) {
    return std::min(alignment_64, alignof(max_align_t));
  }
  return 0;  // Word sizes other than 32/64 are not supported, will need to update test.
}

static_assert(alignof(fit::function<void(), 0>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::function<void(), 1>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::function<int(), 4>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::function<bool(), 8>) == ExpectedAlignment(8, 8), "");
static_assert(alignof(fit::function<float(int), 9>) == ExpectedAlignment(8, 16), "");
static_assert(alignof(fit::function<float(int), 25>) == ExpectedAlignment(16, 16), "");
static_assert(alignof(fit::inline_function<void(), 1>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::inline_function<void(), 9>) == ExpectedAlignment(8, 16), "");
static_assert(alignof(fit::callback<void(), 0>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::callback<void(), 1>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::callback<int(), 4>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::callback<bool(), 8>) == ExpectedAlignment(8, 8), "");
static_assert(alignof(fit::callback<float(int), 9>) == ExpectedAlignment(8, 16), "");
static_assert(alignof(fit::callback<float(int), 25>) == ExpectedAlignment(16, 16), "");
static_assert(alignof(fit::inline_callback<void(), 1>) == ExpectedAlignment(4, 8), "");
static_assert(alignof(fit::inline_callback<void(), 9>) == ExpectedAlignment(8, 16), "");
static_assert(alignof(fit::inline_callback<void(), 25>) == ExpectedAlignment(16, 16), "");

namespace test_copy_move_constructions {

template <typename F>
class assert_move_only {
  static_assert(!std::is_copy_assignable<F>::value);
  static_assert(!std::is_copy_constructible<F>::value);
  static_assert(std::is_move_assignable<F>::value);
  static_assert(std::is_move_constructible<F>::value);

  // It seems that just testing `!std::is_copy_assignable<F>` 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<F>` is false.
  template <typename T, typename = void>
  struct test : std::false_type {};
  template <typename T>
  struct test<T, std::void_t<decltype(T::v1 = T::v2)>> : std::true_type {};

  struct NoAssign {
    static F v1;
    static F v2;
  };
  static_assert(!test<NoAssign>::value);

  struct CanAssign {
    static int v1;
    static int v2;
  };
  static_assert(test<CanAssign>::value);

  template <typename T, typename = void>
  struct test_construct : std::false_type {};
  template <typename T>
  struct test_construct<T, std::void_t<decltype(T{std::declval<const T&>()})>> : std::true_type {};

  static_assert(!test_construct<F>::value);
  static_assert(test_construct<int>::value);
};

template class assert_move_only<fit::function<void()>>;
template class assert_move_only<fit::callback<void()>>;

}  // namespace test_copy_move_constructions

namespace test_conversions {
static_assert(std::is_convertible<Closure, fit::function<Closure>>::value, "");
static_assert(std::is_convertible<BinaryOp, fit::function<BinaryOp>>::value, "");
static_assert(std::is_assignable<fit::function<Closure>, Closure>::value, "");
static_assert(std::is_assignable<fit::function<BinaryOp>, BinaryOp>::value, "");

static_assert(std::is_assignable<fit::function<BooleanGenerator>, IntGenerator>::value, "");
static_assert(std::is_assignable<fit::function<BuildableFromIntGenerator>, IntGenerator>::value,
              "");
static_assert(!std::is_assignable<fit::function<IntGenerator>, BuildableFromIntGenerator>::value,
              "");

static_assert(!std::is_convertible<BinaryOp, fit::function<Closure>>::value, "");
static_assert(!std::is_convertible<Closure, fit::function<BinaryOp>>::value, "");
static_assert(!std::is_assignable<fit::function<Closure>, BinaryOp>::value, "");
static_assert(!std::is_assignable<fit::function<BinaryOp>, Closure>::value, "");

static_assert(!std::is_convertible<ClosureWrongReturnType, fit::function<Closure>>::value, "");
static_assert(!std::is_convertible<BinaryOpWrongReturnType, fit::function<BinaryOp>>::value, "");
static_assert(!std::is_assignable<fit::function<Closure>, ClosureWrongReturnType>::value, "");
static_assert(!std::is_assignable<fit::function<BinaryOp>, BinaryOpWrongReturnType>::value, "");

static_assert(!std::is_convertible<void, fit::function<Closure>>::value, "");
static_assert(!std::is_convertible<void, fit::function<BinaryOp>>::value, "");
static_assert(!std::is_assignable<void, fit::function<Closure>>::value, "");
static_assert(!std::is_assignable<void, fit::function<BinaryOp>>::value, "");

static_assert(std::is_same<fit::function<BinaryOp>::result_type, int>::value, "");
static_assert(std::is_same<fit::callback<BinaryOp>::result_type, int>::value, "");
}  // namespace test_conversions

TEST(FunctionTests, closure_fit_function_Closure) { closure<fit::function<Closure>>(); }
TEST(FunctionTests, binary_op_fit_function_BinaryOp) { binary_op<fit::function<BinaryOp>>(); }
TEST(FunctionTests, closure_fit_function_Closure_0u) { closure<fit::function<Closure, 0u>>(); }
TEST(FunctionTests, binary_op_fit_function_BinaryOp_0u) {
  binary_op<fit::function<BinaryOp, 0u>>();
}
TEST(FunctionTests, closure_fit_function_Closure_HugeCallableSize) {
  closure<fit::function<Closure, HugeCallableSize>>();
}
TEST(FunctionTests, binary_op_fit_function_BinaryOp_HugeCallableSize) {
  binary_op<fit::function<BinaryOp, HugeCallableSize>>();
}
TEST(FunctionTests, closure_fit_inline_function_Closure_HugeCallableSize) {
  closure<fit::inline_function<Closure, HugeCallableSize>>();
}
TEST(FunctionTests, binary_op_fit_inline_function_BinaryOp_HugeCallableSize) {
  binary_op<fit::inline_function<BinaryOp, HugeCallableSize>>();
}
TEST(FunctionTests, closure_fit_function_Closure_SingleObjectAllocator) {
  closure<
      fit::function<Closure, fit::default_inline_target_size, SingleObjectAllocator<std::byte>>>();
}
TEST(FunctionTests, binary_op_fit_function_BinaryOp_SingleObjectAllocator) {
  binary_op<
      fit::function<BinaryOp, fit::default_inline_target_size, SingleObjectAllocator<std::byte>>>();
}

TEST(FunctionTests, bind_return_reference) {
  struct TestClass {
    int& member() { return member_; }
    int member_ = 0;
  };

  TestClass instance;
  // Ensure that references to the original values are returned, not copies.
  fit::function<int&()> func = fit::bind_member<&TestClass::member>(&instance);
  EXPECT_EQ(&func(), &instance.member_);

  fit::function<int&()> func_deprecated = fit::bind_member(&instance, &TestClass::member);
  EXPECT_EQ(&func_deprecated(), &instance.member_);
}

}  // namespace
