/* * Copyright 2019 Google LLC * * 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. */ #include "fcp/base/meta.h" #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" namespace fcp { using ::testing::Eq; using ::testing::Not; struct R { virtual ~R() = default; virtual bool Virt1(int i) = 0; virtual bool Virt2(int i, int j) = 0; virtual void Virt3() = 0; int NonVirt1() { return 1; } int NonVirt2() { return 2; } char field; }; struct S { virtual ~S() = default; virtual bool Virt1(int i) = 0; virtual bool Virt2(int i, int j) = 0; virtual void Virt3() = 0; int NonVirt1() { return 1; } int NonVirt2() { return 2; } char field; }; // // Compile-time tests for MemberPointerTraits // #define STATIC_ASSERT_TARGET_TYPE(member, type) \ static_assert( \ std::is_same::TargetType, \ type>::value, \ "Incorrect target type from MemberPointerTraits"); // For some reason the linter otherwise thinks e.g. 'bool(int)' is an old-style // cast. template struct Id { using Type = T; }; STATIC_ASSERT_TARGET_TYPE(Virt1, Id::Type); STATIC_ASSERT_TARGET_TYPE(Virt2, Id::Type); STATIC_ASSERT_TARGET_TYPE(Virt3, Id::Type); STATIC_ASSERT_TARGET_TYPE(NonVirt1, Id::Type); STATIC_ASSERT_TARGET_TYPE(NonVirt2, Id::Type); STATIC_ASSERT_TARGET_TYPE(field, Id::Type); // // End compile-time tests for MemberPointerTraits // template struct TypeIdHolder { static constexpr char kTarget = '\0'; }; template constexpr char TypeIdHolder::kTarget; // This gives us a unique runtime value per unique type - with which its much // easier to verify uniqueness (below). template static constexpr char const* TypeId() { return &TypeIdHolder::kTarget; } // // LIFT_MEMBER_TO_TYPE // TEST(MetaTest, MemberTagUniqueness) { std::vector type_ids = { TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), TypeId(), }; for (int i = 0; i < type_ids.size(); i++) { for (int j = 0; j < type_ids.size(); j++) { if (i == j) { continue; } EXPECT_THAT(type_ids[i], Not(Eq(type_ids[j]))) << "Member tags must be unique"; } } } int PokeMemberCase(LIFT_MEMBER_TO_TYPE(S, Virt1)) { return 1; } int PokeMemberCase(LIFT_MEMBER_TO_TYPE(S, Virt2)) { return 2; } template int PokeMember() { return PokeMemberCase(LIFT_MEMBER_POINTER_TO_TYPE(M){}); } TEST(MetaTest, MemberTagDispatch) { EXPECT_THAT((PokeMember()), Eq(1)); EXPECT_THAT((PokeMember()), Eq(2)); } // // CastContainerElements // struct X { static constexpr int tag() { return 1; } }; struct Y { static constexpr int tag() { return 2; } }; template struct TypedVal { int value; bool operator==(TypedVal const& other) const { return other.value == value; } bool operator!=(TypedVal const& other) const { return !(*this == other); } }; struct UntypedVal { int tag; int value; }; struct CastValByTag { template using TargetType = std::optional>; template TargetType Cast(UntypedVal const& val) const { if (val.tag == T::tag()) { return TypedVal{val.value}; } else { return std::nullopt; } } }; TEST(MetaTest, CastContainerElements_AllSuccess) { std::vector v{ {X::tag(), 123}, {Y::tag(), 456}, {X::tag(), 789} }; auto actual = CastContainerElements(v, CastValByTag{}); auto expected = std::make_tuple(absl::make_optional(TypedVal{123}), absl::make_optional(TypedVal{456}), absl::make_optional(TypedVal{789})); EXPECT_THAT(actual, Eq(expected)); } TEST(MetaTest, CastContainerElements_AllSuccess_Pack) { std::vector v{ {X::tag(), 123}, {Y::tag(), 456}, {X::tag(), 789} }; // This uses the Pack<> overload instead. auto actual = CastContainerElements(Pack{}, v, CastValByTag{}); auto expected = std::make_tuple(absl::make_optional(TypedVal{123}), absl::make_optional(TypedVal{456}), absl::make_optional(TypedVal{789})); EXPECT_THAT(actual, Eq(expected)); } TEST(MetaTest, CastContainerElements_OneFails) { std::vector v{ {X::tag(), 123}, {X::tag(), 456}, {X::tag(), 789} }; // Second element does not have the tag for Y. auto actual = CastContainerElements(v, CastValByTag{}); auto expected = std::make_tuple(absl::make_optional(TypedVal{123}), std::optional>(std::nullopt), absl::make_optional(TypedVal{789})); EXPECT_THAT(actual, Eq(expected)); } // // MAKE_LINK and LinkedType<> // namespace links { namespace a { struct A1 {}; struct A2 {}; struct A3 {}; MAKE_LINK(A1, A2); } // namespace a namespace b { struct B1 {}; MAKE_LINK(B1, a::A3); } // namespace b } // namespace links static_assert(std::is_same, links::a::A2>::value, "A1 -> A2"); static_assert(HasLinkedType(), "A1 -> A2"); static_assert(std::is_same, void>::value, "A2 -/>"); static_assert(!HasLinkedType(), "A2 -/>"); static_assert(std::is_same, void>::value, "A3 -/>"); static_assert(!HasLinkedType(), "A3 -/>"); static_assert(std::is_same, links::a::A3>::value, "b::B1 -> a::A3"); static_assert(HasLinkedType(), "b::B1 -> a::A3"); // // Pack<> // template constexpr Unit CheckUnpack() { static_assert(std::is_same::value, "A1 == X"); static_assert(std::is_same::value, "A2 == Y"); static_assert(I1 == 0, "I1 == 0"); static_assert(I2 == 1, "I2 == 0"); return {}; } template constexpr Unit UsePack(Pack, absl::index_sequence) { return CheckUnpack(); } template constexpr Unit MakeAndUsePack() { return UsePack(Pack{}, Pack::MakeIndexSequence()); } static_assert(MakeAndUsePack().True(), "Pack<>"); // // LiftVoidReturn // TEST(MetaTest, LiftVoidReturn_Void) { int counter = 0; std::function f = [&counter]() { counter++; }; f(); EXPECT_THAT(counter, Eq(1)); auto f_wrapped = LiftVoidReturn(f); EXPECT_THAT(f_wrapped(), Eq(Unit{})); EXPECT_THAT(counter, Eq(2)); } TEST(MetaTest, LiftVoidReturn_Void_Args) { int counter = 0; std::function f = [&counter](int i) { counter += i; }; f(10); EXPECT_THAT(counter, Eq(10)); auto f_wrapped = LiftVoidReturn(f); EXPECT_THAT(f_wrapped(32), Eq(Unit{})); EXPECT_THAT(counter, Eq(42)); } TEST(MetaTest, LiftVoidReturn_NonVoid) { int counter = 0; std::function f = [&counter](int i) { counter += i; return counter; }; EXPECT_THAT(f(10), Eq(10)); EXPECT_THAT(counter, Eq(10)); auto f_wrapped = LiftVoidReturn(f); EXPECT_THAT(f_wrapped(32), Eq(42)); EXPECT_THAT(counter, Eq(42)); } TEST(MetaTest, LiftVoidReturn_Mutable) { int r = -1; auto f = [&r, counter = 0]() mutable { counter++; r = counter; }; f(); EXPECT_THAT(r, Eq(1)); auto f_wrapped = LiftVoidReturn(f); EXPECT_THAT(f_wrapped(), Eq(Unit{})); EXPECT_THAT(r, Eq(2)); } TEST(MetaTest, LiftVoidReturn_MutableAndMoveOnly) { int r = -1; auto f = [&r, counter = std::make_unique(0)]() mutable { (*counter)++; r = *counter; }; f(); EXPECT_THAT(r, Eq(1)); auto f_wrapped = LiftVoidReturn(std::move(f)); EXPECT_THAT(f_wrapped(), Eq(Unit{})); EXPECT_THAT(r, Eq(2)); } // // FunctionTraits // #define STATIC_ASSERT_FUNCTION_TRAITS(fn, r, ...) \ static_assert(std::is_same::ResultType, r>::value, \ "Incorrect result type from FunctionTraits"); \ static_assert( \ std::is_same::ArgPackType, Pack<__VA_ARGS__>>::value, \ "Incorrect arg pack from FunctionTraits") STATIC_ASSERT_FUNCTION_TRAITS(void(), void); STATIC_ASSERT_FUNCTION_TRAITS(void(int, char), void, int, char); STATIC_ASSERT_FUNCTION_TRAITS(Identity, bool, char const*, int); TEST(MetaTest, IsTypeOneOf) { static_assert(IsTypeOneOf()); static_assert(IsTypeOneOf()); static_assert(IsTypeOneOf()); static_assert(!IsTypeOneOf()); static_assert(!IsTypeOneOf()); } TEST(MetaTest, IsSubsetOf) { using T1 = Pack; using T2 = Pack; using T3 = Pack; static_assert(IsSubsetOf::value); static_assert(IsSubsetOf::value); static_assert(IsSubsetOf::value); static_assert(IsSubsetOf::value); static_assert(!IsSubsetOf::value); } } // namespace fcp