| /* |
| * 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 <mediautils/InPlaceFunction.h> |
| |
| #include <type_traits> |
| |
| #include <gtest/gtest.h> |
| #include <log/log.h> |
| |
| 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<size_t(size_t)> func; |
| { |
| BigCallable test{&x, 2, 3}; |
| func = test; |
| } |
| EXPECT_EQ(func(2), 8ull); |
| EXPECT_EQ(x, 232ull + 5); |
| } |
| |
| TEST(InPlaceFunctionTests, Invalid) { |
| InPlaceFunction<size_t(size_t)> func; |
| EXPECT_TRUE(!func); |
| InPlaceFunction<size_t(size_t)> func2{nullptr}; |
| EXPECT_TRUE(!func2); |
| InPlaceFunction<size_t(size_t)> func3 = [](size_t x) { return x; }; |
| EXPECT_TRUE(!(!func3)); |
| func3 = nullptr; |
| EXPECT_TRUE(!func3); |
| } |
| |
| TEST(InPlaceFunctionTests, MultiArg) { |
| InPlaceFunction<size_t(size_t, size_t, size_t)> 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<size_t()> func{noisy}; |
| EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor |
| EXPECT_EQ(func(), 17ull); |
| Record record2; |
| Noisy noisy2{record2, 13}; |
| InPlaceFunction<size_t()> 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<size_t()> func{noisy}; |
| EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor |
| EXPECT_EQ(func(), 17ull); |
| InPlaceFunction<size_t()> func2{func}; |
| EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor |
| EXPECT_EQ(func2(), 17ull); |
| } |
| Record record; |
| Noisy noisy{record, 13}; |
| InPlaceFunction<size_t()> func{noisy}; |
| EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor |
| EXPECT_EQ(func(), 13ull); |
| InPlaceFunction<size_t()> 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<bool>(func), static_cast<bool>(func2)); |
| EXPECT_EQ(func(), 13ull); |
| } |
| |
| TEST(InPlaceFunctionTests, Dtor) { |
| Record record; |
| { |
| InPlaceFunction<size_t()> 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<size_t()> 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<size_t()> 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<size_t()> func{noisy}; |
| EXPECT_EQ(func(), 17ull); |
| InPlaceFunction<size_t()> 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<bool>(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<size_t()> func{noisy}; |
| EXPECT_EQ(func(), 17ull); |
| InPlaceFunction<size_t()> 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<bool>(func2)); |
| EXPECT_EQ(func2(), 13ull); |
| } |
| EXPECT_EQ(record2, Record(1, 1, 2)); // move, copy, dtor |
| } |
| } |
| |
| TEST(InPlaceFunctionTests, Swap) { |
| Record record1; |
| Record record2; |
| InPlaceFunction<size_t()> func1 = Noisy{record1, 5}; |
| InPlaceFunction<size_t()> 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<size_t(), 16> func2 = noisy; |
| EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor |
| { |
| InPlaceFunction<size_t(), 32> 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<size_t(), 16> func2 = noisy; |
| EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor |
| { |
| InPlaceFunction<size_t(), 32> 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<size_t(), 32> func; |
| { |
| InPlaceFunction<size_t(), 16> 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<size_t(), 32> func; |
| { |
| InPlaceFunction<size_t(), 16> 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<NoMoveCopy&(NoMoveCopy&)> func = lambd; |
| const auto lambd2 = [](NoMoveCopy&& x) -> NoMoveCopy&& { return std::move(x); }; |
| InPlaceFunction<NoMoveCopy && (NoMoveCopy &&)> func2 = lambd2; |
| auto lvalue = NoMoveCopy{}; |
| func(lvalue); |
| func2(NoMoveCopy{}); |
| InPlaceFunction<void(NoMoveCopy&)> func3 = [](const NoMoveCopy&) {}; |
| func3(lvalue); |
| InPlaceFunction<void(NoMoveCopy &&)> func4 = [](const NoMoveCopy&) {}; |
| func4(std::move(lvalue)); |
| InPlaceFunction<void(const NoMoveCopy&)> func5 = [](const NoMoveCopy&) {}; |
| func5(lvalue); |
| InPlaceFunction<void(const NoMoveCopy&&)> func6 = [](const NoMoveCopy&) {}; |
| func6(std::move(lvalue)); |
| InPlaceFunction<void(const NoMoveCopy&&)> func7 = [](const NoMoveCopy&&) {}; |
| func7(std::move(lvalue)); |
| InPlaceFunction<void(NoMoveCopy &&)> func8 = [](const NoMoveCopy&&) {}; |
| func8(std::move(lvalue)); |
| |
| { |
| Record record; |
| Noisy noisy{record, 5}; |
| const auto lambd3 = [](Noisy) {}; |
| InPlaceFunction<void(Noisy)> 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<void(Noisy)> 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<void(size_t)> func = [](size_t x) -> size_t { return x; }; |
| func(5); |
| InPlaceFunction<void(void)> func2 = []() -> size_t { return 5; }; |
| func2(); |
| } |
| NoMoveCopy foo() { |
| return NoMoveCopy(); |
| } |
| struct Test { |
| NoMoveCopy operator()() { return NoMoveCopy{}; } |
| }; |
| |
| TEST(InPlaceFunctionTests, FullElision) { |
| InPlaceFunction<NoMoveCopy()> func = foo; |
| } |
| |
| TEST(InPlaceFunctionTests, ReturnConversion) { |
| const auto lambd = [](int&& x) -> int&& { return std::move(x); }; |
| InPlaceFunction<int && (int&& x)> func = lambd; |
| func(5); |
| InPlaceFunction<void(int)> func3 = [](double) {}; |
| func3(5); |
| InPlaceFunction<double()> 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<int()> func = Overloaded{}; |
| EXPECT_EQ(func(), 2); |
| EXPECT_EQ(std::move(func()), 2); |
| } |
| |
| template <class T, class U, class = void> |
| struct can_assign : std::false_type {}; |
| |
| template <class T, class U> |
| struct can_assign<T, U, typename std::void_t<decltype(T().operator=(U()))>> : std::true_type {}; |
| |
| template <class From, class To, bool Expected> |
| static constexpr bool Convertible = |
| (can_assign<To, From>::value == |
| std::is_constructible_v<To, From>)&&(std::is_constructible_v<To, From> == Expected); |
| |
| struct TooBig { |
| std::array<uint64_t, 5> big = {1, 2, 3, 4, 5}; |
| size_t operator()() { return static_cast<size_t>(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<size_t(), 32>, InPlaceFunction<size_t(), 32>, true>); |
| static_assert( |
| Convertible<InPlaceFunction<size_t(size_t), 32>, InPlaceFunction<size_t(), 32>, false>); |
| static_assert(Convertible<InPlaceFunction<void(), 32>, InPlaceFunction<size_t(), 32>, false>); |
| static_assert(Convertible<TooBig, InPlaceFunction<size_t(), 32>, false>); |
| static_assert(Convertible<TooBig, InPlaceFunction<size_t(), 40>, true>); |
| static_assert(Convertible<NotCallable, InPlaceFunction<size_t(), 40>, false>); |
| static_assert(Convertible<WrongArg, InPlaceFunction<void(size_t), 40>, false>); |
| static_assert(Convertible<WrongRet, InPlaceFunction<size_t(size_t), 40>, false>); |
| // Void returning functions are modelled by any return type |
| static_assert(Convertible<WrongRet, InPlaceFunction<void(size_t), 40>, true>); |
| |
| // Check constructibility/assignability from smaller function types |
| static_assert(Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(), 24>, false>); |
| static_assert(Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(), 40>, true>); |
| static_assert( |
| Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(size_t), 40>, false>); |
| static_assert( |
| Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<NotCallable(), 40>, false>); |
| |
| struct BadLambd { |
| int operator()(int&& x) { return std::move(x); } |
| }; |
| |
| static_assert(Convertible<BadLambd, InPlaceFunction<int(int&&), 32>, true>); |
| static_assert(Convertible<BadLambd, InPlaceFunction<int&(int&&), 32>, false>); |
| static_assert(Convertible<BadLambd, InPlaceFunction<const int&(int&&), 32>, false>); |
| static_assert(Convertible<BadLambd, InPlaceFunction<int && (int&&), 32>, false>); |
| static_assert(Convertible<BadLambd, InPlaceFunction<const int && (int&&), 32>, 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<ConvertCallable, InPlaceFunction<Derived&()>, false>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Base&()>, false>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Derived()>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Base()>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Converted()>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Converted&()>, false>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Converted && ()>, false>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Converted&()>, false>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Converted && ()>, false>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Derived&(Derived&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Base&(Derived&)>, true>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Derived && (Derived &&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<Base && (Derived &&)>, true>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Derived&(const Derived&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Base&(const Derived&)>, true>); |
| |
| static_assert( |
| Convertible<ConvertCallable, InPlaceFunction<const Derived && (const Derived&&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Base && (const Derived&&)>, true>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Derived&(Derived&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Base&(Derived&)>, true>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Derived && (Derived &&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Base && (Derived &&)>, true>); |
| |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Derived&(Derived&&)>, true>); |
| static_assert(Convertible<ConvertCallable, InPlaceFunction<const Base&(Derived&&)>, true>); |