#include "catch2/catch.hpp" #include #include using namespace fastsignals; namespace { int Abs(int x) { return x >= 0 ? x : -x; } int Sum(int a, int b) { return a + b; } void InplaceAbs(int& x) { x = Abs(x); } std::string GetStringHello() { return "hello"; } class AbsFunctor { public: int operator()(int x) const { return Abs(x); } }; class SumFunctor { public: int operator()(int a, int b) const { return Sum(a, b); } }; class InplaceAbsFunctor { public: void operator()(int& x) /* non-const */ { if (m_calledOnce) { abort(); } m_calledOnce = true; InplaceAbs(x); } private: bool m_calledOnce = false; }; class GetStringFunctor { public: explicit GetStringFunctor(const std::string& value) : m_value(value) { } std::string operator()() /* non-const */ { if (m_calledOnce) { abort(); } m_calledOnce = true; return m_value; } private: bool m_calledOnce = false; std::string m_value; }; } // namespace TEST_CASE("Can use free function with 1 argument", "[function]") { function fn = Abs; REQUIRE(fn(10) == 10); REQUIRE(fn(-10) == 10); REQUIRE(fn(0) == 0); } TEST_CASE("Can use free function with 2 arguments", "[function]") { function fn = Sum; REQUIRE(fn(10, 5) == 15); REQUIRE(fn(-10, 0) == -10); } TEST_CASE("Can use free function without arguments", "[function]") { function fn = GetStringHello; REQUIRE(fn() == "hello"); } TEST_CASE("Can use free function without return value", "[function]") { function fn = InplaceAbs; int a = -10; fn(a); REQUIRE(a == 10); } TEST_CASE("Can use lambda with 1 argument", "[function]") { function fn = [](int value) { return Abs(value); }; REQUIRE(fn(10) == 10); REQUIRE(fn(-10) == 10); REQUIRE(fn(0) == 0); } TEST_CASE("Can use lambda with 2 arguments", "[function]") { function fn = [](auto&& a, auto&& b) { return Sum(a, b); }; REQUIRE(fn(10, 5) == 15); REQUIRE(fn(-10, 0) == -10); } TEST_CASE("Can use lambda without arguments", "[function]") { function fn = [] { return GetStringHello(); }; REQUIRE(fn() == "hello"); } TEST_CASE("Can use lambda without return value", "[function]") { bool calledOnce = false; function fn = [calledOnce](auto& value) mutable { if (calledOnce) { abort(); } calledOnce = true; InplaceAbs(value); }; int a = -10; fn(a); REQUIRE(a == 10); } TEST_CASE("Can use functor with 1 argument", "[function]") { function fn = AbsFunctor(); REQUIRE(fn(10) == 10); REQUIRE(fn(-10) == 10); REQUIRE(fn(0) == 0); } TEST_CASE("Can use functor with 2 arguments", "[function]") { function fn = SumFunctor(); REQUIRE(fn(10, 5) == 15); REQUIRE(fn(-10, 0) == -10); } TEST_CASE("Can use functor without arguments", "[function]") { function fn = GetStringFunctor("hello"); REQUIRE(fn() == "hello"); } TEST_CASE("Can use functor without return value", "[function]") { function fn = InplaceAbsFunctor(); int a = -10; fn(a); REQUIRE(a == 10); } TEST_CASE("Can construct function with cons std::function<>&", "[function]") { using BoolCallback = std::function; bool value = false; const BoolCallback& cb = [&value](bool succeed) { value = succeed; }; function fn = cb; fn(true); REQUIRE(value == true); fn(false); REQUIRE(value == false); fn(true); REQUIRE(value == true); } TEST_CASE("Can copy function", "[function]") { unsigned calledCount = 0; bool value = false; function callback = [&](bool gotValue) { ++calledCount; value = gotValue; }; auto callback2 = callback; REQUIRE(calledCount == 0); CHECK(!value); callback(true); REQUIRE(calledCount == 1); CHECK(value); callback2(false); REQUIRE(calledCount == 2); CHECK(!value); } TEST_CASE("Can move function", "[function]") { bool called = false; function callback = [&] { called = true; }; auto callback2(std::move(callback)); REQUIRE_THROWS(callback()); REQUIRE(!called); callback2(); REQUIRE(called); } TEST_CASE("Works when copying self", "[function]") { bool called = false; function callback = [&] { called = true; }; callback = callback; callback(); REQUIRE(called); } TEST_CASE("Can release packed function", "[function]") { function iota = [v = 0]() mutable { return v++; }; REQUIRE(iota() == 0); auto packedFn = std::move(iota).release(); REQUIRE_THROWS_AS(iota(), std::bad_function_call); auto&& proxy = packedFn.get(); REQUIRE(proxy() == 1); REQUIRE(proxy() == 2); } TEST_CASE("Function copy has its own packed function", "[function]") { function iota = [v = 0]() mutable { return v++; }; REQUIRE(iota() == 0); auto iotaCopy(iota); REQUIRE(iota() == 1); REQUIRE(iota() == 2); REQUIRE(iotaCopy() == 1); REQUIRE(iotaCopy() == 2); } TEST_CASE("can work with callables that have vtable", "[function]") { class Base { }; class Interface : public Base { public: virtual ~Interface() = default; virtual void operator()() const = 0; }; class Class : public Interface { public: Class(bool* destructorCalled) : m_destructorCalled(destructorCalled) { } ~Class() { *m_destructorCalled = true; } void operator()() const override { } bool* m_destructorCalled = nullptr; }; bool destructorCalled = false; { function f = Class(&destructorCalled); f(); auto packed = f.release(); destructorCalled = false; } CHECK(destructorCalled); } TEST_CASE("can work with callables with virtual inheritance", "[function]") { struct A { void operator()() const { m_called = true; } ~A() { *m_destructorCalled = true; } mutable bool m_called = false; bool* m_destructorCalled = nullptr; }; struct B : public virtual A { }; struct C : public virtual A { }; struct D : virtual public B , virtual public C { D(bool* destructorCalled) { m_destructorCalled = destructorCalled; } using A::operator(); }; bool destructorCalled = false; { function f = D(&destructorCalled); f(); auto packed = f.release(); destructorCalled = false; } CHECK(destructorCalled); } TEST_CASE("uses copy constructor if callable's move constructor throws", "[function]") { struct Callable { Callable() = default; Callable(Callable&&) { throw std::runtime_error("throw"); } Callable(const Callable& other) = default; void operator()() const { } }; Callable c; function f(c); auto f2 = std::move(f); f2(); CHECK_THROWS(f()); } TEST_CASE("uses move constructor if it is noexcept", "[function]") { struct Callable { Callable() = default; Callable(Callable&& other) noexcept = default; Callable(const Callable&) { throw std::runtime_error("throw"); } void operator()() const { } }; Callable c; function f(std::move(c)); auto f2 = std::move(f); f2(); CHECK_THROWS(f()); } TEST_CASE("can copy and move empty function", "[function]") { function f; auto f2 = f; auto f3 = std::move(f); } TEST_CASE("properly copies callable on assignment", "[function]") { struct Callable { Callable(int& aliveCounter) : m_aliveCounter(&aliveCounter) { ++*m_aliveCounter; } Callable(const Callable& other) : m_aliveCounter(other.m_aliveCounter) { if (m_aliveCounter) { ++*m_aliveCounter; } } Callable(Callable&& other) noexcept : m_aliveCounter(other.m_aliveCounter) { other.m_aliveCounter = nullptr; } ~Callable() { if (m_aliveCounter) { --*m_aliveCounter; } } void operator()() const { } int* m_aliveCounter = nullptr; }; int aliveCounter1 = 0; int aliveCounter2 = 0; function f = Callable(aliveCounter1); function f2 = Callable(aliveCounter2); CHECK(aliveCounter1 == 1); CHECK(aliveCounter2 == 1); f = f2; CHECK(aliveCounter1 == 0); CHECK(aliveCounter2 == 2); f = function(); f2 = function(); CHECK(aliveCounter1 == 0); CHECK(aliveCounter2 == 0); } TEST_CASE("copy assignment operator provides strong exception safety", "[function]") { struct State { int callCount = 0; bool throwOnCopy = false; }; struct Callable { Callable(State& state) : state(&state) { } void operator()() { ++state->callCount; } Callable(const Callable& other) : state(other.state) { if (state->throwOnCopy) { throw std::runtime_error("throw on request"); } } State* state = nullptr; }; static_assert(!detail::can_use_inplace_buffer); State srcState; State dstState; function srcFn(Callable{ srcState }); function dstFn(Callable{ dstState }); srcFn(); dstFn(); REQUIRE(srcState.callCount == 1); REQUIRE(dstState.callCount == 1); srcState.throwOnCopy = true; REQUIRE_THROWS_AS(dstFn = srcFn, std::runtime_error); // srcFn and dstFn must not be emptied even if assignment throws REQUIRE_NOTHROW(srcFn()); REQUIRE_NOTHROW(dstFn()); // srcFn and dstFn must keep their state REQUIRE(srcState.callCount == 2); REQUIRE(dstState.callCount == 2); // The next copy will succeed srcState.throwOnCopy = false; REQUIRE_NOTHROW(dstFn = srcFn); // Both functions are usable REQUIRE_NOTHROW(srcFn()); REQUIRE_NOTHROW(dstFn()); // After assignment, dstFn and srcFn refer the same state - srcState REQUIRE(srcState.callCount == 4); REQUIRE(dstState.callCount == 2); } TEST_CASE("assignment of variously allocated functions", "[function]") { int heapCalls = 0; auto onHeap = [&heapCalls, largeVar = std::array()]() mutable { std::fill(largeVar.begin(), largeVar.end(), "large string to be allocated on heap instead of stack"); ++heapCalls; }; int stackCalls = 0; auto onStack = [&stackCalls] { ++stackCalls; }; static_assert(detail::can_use_inplace_buffer>); static_assert(!detail::can_use_inplace_buffer>); using Fn = function; { Fn heap(onHeap); Fn stack(onStack); heap = stack; heap(); REQUIRE(stackCalls == 1); REQUIRE(heapCalls == 0); } { Fn heap(onHeap); Fn stack(onStack); stack = heap; stack(); REQUIRE(stackCalls == 1); REQUIRE(heapCalls == 1); } { Fn heap(onHeap); Fn heap1(onHeap); heap = heap1; heap(); REQUIRE(stackCalls == 1); REQUIRE(heapCalls == 2); } { Fn stack(onStack); Fn stack1(onStack); stack = stack1; stack(); REQUIRE(stackCalls == 2); REQUIRE(heapCalls == 2); } { Fn heap(onHeap); Fn empty; heap = empty; REQUIRE_THROWS(heap()); REQUIRE(stackCalls == 2); REQUIRE(heapCalls == 2); } { Fn stack(onStack); Fn empty; stack = empty; REQUIRE_THROWS(stack()); REQUIRE(stackCalls == 2); REQUIRE(heapCalls == 2); } { Fn empty; Fn heap(onHeap); empty = heap; empty(); REQUIRE(stackCalls == 2); REQUIRE(heapCalls == 3); } { Fn empty; Fn stack(onStack); empty = stack; empty(); REQUIRE(stackCalls == 3); REQUIRE(heapCalls == 3); } { Fn empty; Fn empty1; empty = empty1; REQUIRE_THROWS(empty()); REQUIRE(stackCalls == 3); REQUIRE(heapCalls == 3); } }