/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "inplace_function_tests" #include #include #include #include using namespace android; using namespace android::mediautils; struct BigCallable { BigCallable(size_t* x, size_t val1, size_t val2) : ptr(x), a(val1), b(val2) {} size_t* ptr; size_t a; size_t b; size_t operator()(size_t input) const { *ptr += a * 100 + b * 10 + input; return 8; } }; TEST(InPlaceFunctionTests, Basic) { size_t x = 5; InPlaceFunction func; { BigCallable test{&x, 2, 3}; func = test; } EXPECT_EQ(func(2), 8ull); EXPECT_EQ(x, 232ull + 5); } TEST(InPlaceFunctionTests, Invalid) { InPlaceFunction func; EXPECT_TRUE(!func); InPlaceFunction func2{nullptr}; EXPECT_TRUE(!func2); InPlaceFunction func3 = [](size_t x) { return x; }; EXPECT_TRUE(!(!func3)); func3 = nullptr; EXPECT_TRUE(!func3); } TEST(InPlaceFunctionTests, MultiArg) { InPlaceFunction func = [](size_t a, size_t b, size_t c) { return a + b + c; }; EXPECT_EQ(func(2, 3, 5), 2ull + 3 + 5); } struct Record { Record(size_t m, size_t c, size_t d) : move_called(m), copy_called(c), dtor_called(d) {} Record() {} size_t move_called = 0; size_t copy_called = 0; size_t dtor_called = 0; friend std::ostream& operator<<(std::ostream& os, const Record& record) { return os << "Record, moves: " << record.move_called << ", copies: " << record.copy_called << ", dtor: " << record.dtor_called << '\n'; } }; bool operator==(const Record& lhs, const Record& rhs) { return lhs.move_called == rhs.move_called && lhs.copy_called == rhs.copy_called && lhs.dtor_called == rhs.dtor_called; } struct Noisy { Record& ref; size_t state; Noisy(Record& record, size_t val) : ref(record), state(val) {} Noisy(const Noisy& other) : ref(other.ref), state(other.state) { ref.copy_called++; } Noisy(Noisy&& other) : ref(other.ref), state(other.state) { ref.move_called++; } ~Noisy() { ref.dtor_called++; } size_t operator()() { return state; } }; TEST(InPlaceFunctionTests, CtorForwarding) { Record record; Noisy noisy{record, 17}; InPlaceFunction func{noisy}; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor EXPECT_EQ(func(), 17ull); Record record2; Noisy noisy2{record2, 13}; InPlaceFunction func2{std::move(noisy2)}; EXPECT_EQ(record2, Record(1, 0, 0)); // move, copy, dtor EXPECT_EQ(func2(), 13ull); } TEST(InPlaceFunctionTests, FunctionCtorForwarding) { { Record record; Noisy noisy{record, 17}; InPlaceFunction func{noisy}; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor EXPECT_EQ(func(), 17ull); InPlaceFunction func2{func}; EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor EXPECT_EQ(func2(), 17ull); } Record record; Noisy noisy{record, 13}; InPlaceFunction func{noisy}; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor EXPECT_EQ(func(), 13ull); InPlaceFunction func2{std::move(func)}; EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor EXPECT_EQ(func2(), 13ull); // We expect moved from functions to still be valid EXPECT_TRUE(!(!func)); EXPECT_EQ(static_cast(func), static_cast(func2)); EXPECT_EQ(func(), 13ull); } TEST(InPlaceFunctionTests, Dtor) { Record record; { InPlaceFunction func; { Noisy noisy{record, 17}; func = noisy; } EXPECT_EQ(func(), 17ull); EXPECT_EQ(record.dtor_called, 1ull); } EXPECT_EQ(record.dtor_called, 2ull); } TEST(InPlaceFunctionTests, Assignment) { { Record record; Record record2; Noisy noisy{record, 17}; Noisy noisy2{record2, 5}; InPlaceFunction func{noisy}; EXPECT_EQ(func(), 17ull); EXPECT_EQ(record.dtor_called, 0ull); func = noisy2; EXPECT_EQ(record.dtor_called, 1ull); EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor EXPECT_EQ(func(), 5ull); } { Record record; Record record2; Noisy noisy{record, 17}; Noisy noisy2{record2, 5}; InPlaceFunction func{noisy}; EXPECT_EQ(func(), 17ull); EXPECT_EQ(record.dtor_called, 0ull); func = std::move(noisy2); EXPECT_EQ(record.dtor_called, 1ull); EXPECT_EQ(record2, Record(1, 0, 0)); // move, copy, dtor EXPECT_EQ(func(), 5ull); } { Record record; Record record2; Noisy noisy{record, 17}; Noisy noisy2{record2, 13}; { InPlaceFunction func{noisy}; EXPECT_EQ(func(), 17ull); InPlaceFunction func2{noisy2}; EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor EXPECT_EQ(record.dtor_called, 0ull); func = func2; EXPECT_EQ(record.dtor_called, 1ull); EXPECT_EQ(func(), 13ull); EXPECT_EQ(record2, Record(0, 2, 0)); // move, copy, dtor EXPECT_TRUE(static_cast(func2)); EXPECT_EQ(func2(), 13ull); } EXPECT_EQ(record2, Record(0, 2, 2)); // move, copy, dtor } { Record record; Record record2; Noisy noisy{record, 17}; Noisy noisy2{record2, 13}; { InPlaceFunction func{noisy}; EXPECT_EQ(func(), 17ull); InPlaceFunction func2{noisy2}; EXPECT_EQ(record.dtor_called, 0ull); EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor func = std::move(func2); EXPECT_EQ(record.dtor_called, 1ull); EXPECT_EQ(func(), 13ull); EXPECT_EQ(record2, Record(1, 1, 0)); // move, copy, dtor // Moved from function is still valid EXPECT_TRUE(static_cast(func2)); EXPECT_EQ(func2(), 13ull); } EXPECT_EQ(record2, Record(1, 1, 2)); // move, copy, dtor } } TEST(InPlaceFunctionTests, Swap) { Record record1; Record record2; InPlaceFunction func1 = Noisy{record1, 5}; InPlaceFunction func2 = Noisy{record2, 7}; EXPECT_EQ(record1, Record(1, 0, 1)); // move, copy, dtor EXPECT_EQ(record2, Record(1, 0, 1)); // move, copy, dtor EXPECT_EQ(func1(), 5ull); EXPECT_EQ(func2(), 7ull); func1.swap(func2); EXPECT_EQ(record1, Record(2, 0, 2)); // move, copy, dtor // An additional move and destroy into the temporary object EXPECT_EQ(record2, Record(3, 0, 3)); // move, copy, dtor EXPECT_EQ(func1(), 7ull); EXPECT_EQ(func2(), 5ull); } TEST(InPlaceFunctionTests, Conversion) { Record record; Noisy noisy{record, 15}; { InPlaceFunction func2 = noisy; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor { InPlaceFunction func{func2}; EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor EXPECT_EQ(func2(), func()); } EXPECT_EQ(record, Record(0, 2, 1)); // move, copy, dtor } EXPECT_EQ(record, Record(0, 2, 2)); // move, copy, dtor } TEST(InPlaceFunctionTests, ConversionMove) { Record record; Noisy noisy{record, 15}; { InPlaceFunction func2 = noisy; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor { InPlaceFunction func{std::move(func2)}; EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor EXPECT_EQ(func2(), func()); } EXPECT_EQ(record, Record(1, 1, 1)); // move, copy, dtor } EXPECT_EQ(record, Record(1, 1, 2)); // move, copy, dtor } TEST(InPlaceFunctionTests, ConversionAssign) { Record record; Noisy noisy{record, 15}; { InPlaceFunction func; { InPlaceFunction func2 = noisy; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor func = func2; EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor EXPECT_EQ(func2(), func()); } EXPECT_EQ(record, Record(0, 2, 1)); // move, copy, dtor } EXPECT_EQ(record, Record(0, 2, 2)); // move, copy, dtor } TEST(InPlaceFunctionTests, ConversionAssignMove) { Record record; Noisy noisy{record, 15}; { InPlaceFunction func; { InPlaceFunction func2 = noisy; EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor func = std::move(func2); EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor EXPECT_EQ(func2(), func()); } EXPECT_EQ(record, Record(1, 1, 1)); // move, copy, dtor } EXPECT_EQ(record, Record(1, 1, 2)); // move, copy, dtor } struct NoMoveCopy { NoMoveCopy() = default; NoMoveCopy(const NoMoveCopy&) = delete; NoMoveCopy(NoMoveCopy&&) = delete; }; struct TestCallable { NoMoveCopy& operator()(NoMoveCopy& x) { return x; } }; TEST(InPlaceFunctionTests, ArgumentForwarding) { const auto lambd = [](NoMoveCopy& x) -> NoMoveCopy& { return x; }; InPlaceFunction func = lambd; const auto lambd2 = [](NoMoveCopy&& x) -> NoMoveCopy&& { return std::move(x); }; InPlaceFunction func2 = lambd2; auto lvalue = NoMoveCopy{}; func(lvalue); func2(NoMoveCopy{}); InPlaceFunction func3 = [](const NoMoveCopy&) {}; func3(lvalue); InPlaceFunction func4 = [](const NoMoveCopy&) {}; func4(std::move(lvalue)); InPlaceFunction func5 = [](const NoMoveCopy&) {}; func5(lvalue); InPlaceFunction func6 = [](const NoMoveCopy&) {}; func6(std::move(lvalue)); InPlaceFunction func7 = [](const NoMoveCopy&&) {}; func7(std::move(lvalue)); InPlaceFunction func8 = [](const NoMoveCopy&&) {}; func8(std::move(lvalue)); { Record record; Noisy noisy{record, 5}; const auto lambd3 = [](Noisy) {}; InPlaceFunction func3{lambd3}; EXPECT_EQ(record, Record(0, 0, 0)); // move, copy, dtor func3(std::move(noisy)); EXPECT_EQ(record, Record(2, 0, 2)); // move, copy, dtor } { Record record; Noisy noisy{record, 5}; const auto lambd3 = [](Noisy) {}; InPlaceFunction func3{lambd3}; EXPECT_EQ(record, Record(0, 0, 0)); // move, copy, dtor func3(noisy); EXPECT_EQ(record, Record(1, 1, 2)); // move, copy, dtor } } TEST(InPlaceFunctionTests, VoidFunction) { InPlaceFunction func = [](size_t x) -> size_t { return x; }; func(5); InPlaceFunction func2 = []() -> size_t { return 5; }; func2(); } NoMoveCopy foo() { return NoMoveCopy(); } struct Test { NoMoveCopy operator()() { return NoMoveCopy{}; } }; TEST(InPlaceFunctionTests, FullElision) { InPlaceFunction func = foo; } TEST(InPlaceFunctionTests, ReturnConversion) { const auto lambd = [](int&& x) -> int&& { return std::move(x); }; InPlaceFunction func = lambd; func(5); InPlaceFunction func3 = [](double) {}; func3(5); InPlaceFunction func4 = []() -> int { return 5; }; func4(); } struct Overloaded { int operator()() & { return 2; } int operator()() const& { return 3; } int operator()() && { return 4; } int operator()() const&& { return 5; } }; TEST(InPlaceFunctionTests, OverloadResolution) { InPlaceFunction func = Overloaded{}; EXPECT_EQ(func(), 2); EXPECT_EQ(std::move(func()), 2); } template struct can_assign : std::false_type {}; template struct can_assign> : std::true_type {}; template static constexpr bool Convertible = (can_assign::value == std::is_constructible_v)&&(std::is_constructible_v == Expected); struct TooBig { std::array big = {1, 2, 3, 4, 5}; size_t operator()() { return static_cast(big[0] + big[1] + big[2] + big[3] + big[4]); } }; static_assert(sizeof(TooBig) == 40); struct NotCallable {}; struct WrongArg { void operator()(NotCallable) {} }; struct WrongRet { NotCallable operator()(size_t) { return NotCallable{}; } }; static_assert(Convertible, InPlaceFunction, true>); static_assert( Convertible, InPlaceFunction, false>); static_assert(Convertible, InPlaceFunction, false>); static_assert(Convertible, false>); static_assert(Convertible, true>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, false>); // Void returning functions are modelled by any return type static_assert(Convertible, true>); // Check constructibility/assignability from smaller function types static_assert(Convertible, InPlaceFunction, false>); static_assert(Convertible, InPlaceFunction, true>); static_assert( Convertible, InPlaceFunction, false>); static_assert( Convertible, InPlaceFunction, false>); struct BadLambd { int operator()(int&& x) { return std::move(x); } }; static_assert(Convertible, true>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, false>); struct Base {}; struct Derived : Base {}; struct Converted { Converted(const Derived&) {} }; struct ConvertCallable { Derived operator()() { return Derived{}; } Derived& operator()(Derived& x) { return x; } Derived&& operator()(Derived&& x) { return std::move(x); } const Derived& operator()(const Derived& x) { return x; } const Derived&& operator()(const Derived&& x) { return std::move(x); } }; static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, false>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert( Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>); static_assert(Convertible, true>);