| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/types/strong_alias.h" |
| |
| #include <cstdint> |
| #include <map> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "base/types/supports_ostream_operator.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| #include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h" // no-presubmit-check nogncheck |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| namespace base { |
| |
| namespace { |
| |
| // For test correctnenss, it's important that these getters return lexically |
| // incrementing values as |index| grows. |
| template <typename T> |
| T GetExampleValue(int index); |
| |
| template <> |
| int GetExampleValue<int>(int index) { |
| return 5 + index; |
| } |
| template <> |
| uint64_t GetExampleValue<uint64_t>(int index) { |
| return 500U + index; |
| } |
| |
| template <> |
| std::string GetExampleValue<std::string>(int index) { |
| return std::string('a', index); |
| } |
| |
| template <typename T, typename U> |
| bool StreamOutputSame(const T& a, const U& b) { |
| std::stringstream ssa; |
| ssa << a; |
| std::stringstream ssb; |
| ssb << b; |
| return ssa.str() == ssb.str(); |
| } |
| |
| } // namespace |
| |
| template <typename T> |
| class StrongAliasTest : public ::testing::Test {}; |
| |
| using TestedTypes = ::testing::Types<int, uint64_t, std::string>; |
| TYPED_TEST_SUITE(StrongAliasTest, TestedTypes); |
| |
| TYPED_TEST(StrongAliasTest, ValueAccessesUnderlyingValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| |
| // Const value getter. |
| const FooAlias const_alias(GetExampleValue<TypeParam>(1)); |
| EXPECT_EQ(GetExampleValue<TypeParam>(1), const_alias.value()); |
| static_assert( |
| std::is_const_v< |
| typename std::remove_reference<decltype(const_alias.value())>::type>, |
| "Reference returned by const value getter should be const."); |
| } |
| |
| TYPED_TEST(StrongAliasTest, ExplicitConversionToUnderlyingValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| |
| const FooAlias const_alias(GetExampleValue<TypeParam>(1)); |
| EXPECT_EQ(GetExampleValue<TypeParam>(1), static_cast<TypeParam>(const_alias)); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeCopyConstructed) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| FooAlias alias(GetExampleValue<TypeParam>(0)); |
| FooAlias copy_constructed = alias; |
| EXPECT_EQ(copy_constructed, alias); |
| |
| FooAlias copy_assigned; |
| copy_assigned = alias; |
| EXPECT_EQ(copy_assigned, alias); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeMoveConstructed) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| FooAlias alias(GetExampleValue<TypeParam>(0)); |
| FooAlias move_constructed = std::move(alias); |
| EXPECT_EQ(move_constructed, FooAlias(GetExampleValue<TypeParam>(0))); |
| |
| FooAlias alias2(GetExampleValue<TypeParam>(2)); |
| FooAlias move_assigned; |
| move_assigned = std::move(alias2); |
| EXPECT_EQ(move_assigned, FooAlias(GetExampleValue<TypeParam>(2))); |
| |
| // Check that FooAlias is nothrow move constructible. This matters for |
| // performance when used in std::vectors. |
| static_assert(std::is_nothrow_move_constructible_v<FooAlias>, |
| "Error: Alias is not nothow move constructible"); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeConstructedFromMoveOnlyType) { |
| // Note, using a move-only unique_ptr to T: |
| using FooAlias = StrongAlias<class FooTag, std::unique_ptr<TypeParam>>; |
| |
| FooAlias a(std::make_unique<TypeParam>(GetExampleValue<TypeParam>(0))); |
| EXPECT_EQ(*a.value(), GetExampleValue<TypeParam>(0)); |
| |
| auto bare_value = std::make_unique<TypeParam>(GetExampleValue<TypeParam>(1)); |
| FooAlias b(std::move(bare_value)); |
| EXPECT_EQ(*b.value(), GetExampleValue<TypeParam>(1)); |
| } |
| |
| TYPED_TEST(StrongAliasTest, MutableOperatorArrow) { |
| // Note, using a move-only unique_ptr to T: |
| using Ptr = std::unique_ptr<TypeParam>; |
| using FooAlias = StrongAlias<class FooTag, Ptr>; |
| |
| FooAlias a(std::make_unique<TypeParam>()); |
| EXPECT_TRUE(a.value()); |
| |
| // Check that `a` can be modified through the use of operator->. |
| a->reset(); |
| |
| EXPECT_FALSE(a.value()); |
| } |
| |
| TYPED_TEST(StrongAliasTest, MutableOperatorStar) { |
| // Note, using a move-only unique_ptr to T: |
| using Ptr = std::unique_ptr<TypeParam>; |
| using FooAlias = StrongAlias<class FooTag, Ptr>; |
| |
| FooAlias a(std::make_unique<TypeParam>()); |
| FooAlias b(std::make_unique<TypeParam>()); |
| EXPECT_TRUE(*a); |
| EXPECT_TRUE(*b); |
| |
| // Check that both the mutable l-value and r-value overloads work and we can |
| // move out of the aliases. |
| { Ptr ignore(*std::move(a)); } |
| { Ptr ignore(std::move(*b)); } |
| |
| EXPECT_FALSE(a.value()); |
| EXPECT_FALSE(b.value()); |
| } |
| |
| TYPED_TEST(StrongAliasTest, MutableValue) { |
| // Note, using a move-only unique_ptr to T: |
| using Ptr = std::unique_ptr<TypeParam>; |
| using FooAlias = StrongAlias<class FooTag, Ptr>; |
| |
| FooAlias a(std::make_unique<TypeParam>()); |
| FooAlias b(std::make_unique<TypeParam>()); |
| EXPECT_TRUE(a.value()); |
| EXPECT_TRUE(b.value()); |
| |
| // Check that both the mutable l-value and r-value overloads work and we can |
| // move out of the aliases. |
| { Ptr ignore(std::move(a).value()); } |
| { Ptr ignore(std::move(b.value())); } |
| |
| EXPECT_FALSE(a.value()); |
| EXPECT_FALSE(b.value()); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeWrittenToOutputStream) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| |
| const FooAlias a(GetExampleValue<TypeParam>(0)); |
| EXPECT_TRUE(StreamOutputSame(GetExampleValue<TypeParam>(0), a)) << a; |
| } |
| |
| TYPED_TEST(StrongAliasTest, SizeSameAsUnderlyingType) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| static_assert(sizeof(FooAlias) == sizeof(TypeParam), |
| "StrongAlias should be as large as the underlying type."); |
| } |
| |
| TYPED_TEST(StrongAliasTest, IsDefaultConstructible) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| static_assert(std::is_default_constructible_v<FooAlias>, |
| "Should be possible to default-construct a StrongAlias."); |
| static_assert( |
| std::is_trivially_default_constructible_v<FooAlias> == |
| std::is_trivially_default_constructible_v<TypeParam>, |
| "Should be possible to trivially default-construct a StrongAlias iff the " |
| "underlying type is trivially default constructible."); |
| } |
| |
| TEST(StrongAliasTest, TrivialTypeAliasIsStandardLayout) { |
| using FooAlias = StrongAlias<class FooTag, int>; |
| static_assert(std::is_standard_layout_v<FooAlias>, |
| "int-based alias should have standard layout. "); |
| static_assert(std::is_trivially_copyable_v<FooAlias>, |
| "int-based alias should be trivially copyable. "); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CannotBeCreatedFromDifferentAlias) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| using BarAlias = StrongAlias<class BarTag, TypeParam>; |
| static_assert(!std::is_constructible_v<FooAlias, BarAlias>, |
| "Should be impossible to construct FooAlias from a BarAlias."); |
| static_assert(!std::is_convertible_v<BarAlias, FooAlias>, |
| "Should be impossible to convert a BarAlias into FooAlias."); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| static_assert(!std::is_convertible_v<FooAlias, TypeParam>, |
| "Should be impossible to implicitly convert a StrongAlias into " |
| "an underlying type."); |
| } |
| |
| TYPED_TEST(StrongAliasTest, ComparesEqualToSameValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| // Comparison to self: |
| const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0)); |
| EXPECT_EQ(a, a); |
| EXPECT_FALSE(a != a); |
| EXPECT_TRUE(a >= a); |
| EXPECT_TRUE(a <= a); |
| EXPECT_FALSE(a > a); |
| EXPECT_FALSE(a < a); |
| // Comparison to other equal object: |
| const FooAlias b = FooAlias(GetExampleValue<TypeParam>(0)); |
| EXPECT_EQ(a, b); |
| EXPECT_FALSE(a != b); |
| EXPECT_TRUE(a >= b); |
| EXPECT_TRUE(a <= b); |
| EXPECT_FALSE(a > b); |
| EXPECT_FALSE(a < b); |
| } |
| |
| TYPED_TEST(StrongAliasTest, ComparesCorrectlyToDifferentValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| const FooAlias a = FooAlias(GetExampleValue<TypeParam>(0)); |
| const FooAlias b = FooAlias(GetExampleValue<TypeParam>(1)); |
| EXPECT_NE(a, b); |
| EXPECT_FALSE(a == b); |
| EXPECT_TRUE(b >= a); |
| EXPECT_TRUE(a <= b); |
| EXPECT_TRUE(b > a); |
| EXPECT_TRUE(a < b); |
| } |
| |
| TEST(StrongAliasTest, CanBeDerivedFrom) { |
| // Aliases can be enriched by custom operations or validations if needed. |
| // Ideally, one could go from a 'using' declaration to a derived class to add |
| // those methods without the need to change any other code. |
| class CountryCode : public StrongAlias<CountryCode, std::string> { |
| public: |
| CountryCode(const std::string& value) |
| : StrongAlias<CountryCode, std::string>::StrongAlias(value) { |
| if (value_.length() != 2) { |
| // Country code invalid! |
| value_.clear(); // is_null() will return true. |
| } |
| } |
| |
| bool is_null() const { return value_.empty(); } |
| }; |
| |
| CountryCode valid("US"); |
| EXPECT_FALSE(valid.is_null()); |
| |
| CountryCode invalid("United States"); |
| EXPECT_TRUE(invalid.is_null()); |
| } |
| |
| TEST(StrongAliasTest, CanWrapComplexStructures) { |
| // A pair of strings implements odering and can, in principle, be used as |
| // a base of StrongAlias. |
| using PairOfStrings = std::pair<std::string, std::string>; |
| using ComplexAlias = StrongAlias<class FooTag, PairOfStrings>; |
| |
| ComplexAlias a1{std::make_pair("aaa", "bbb")}; |
| ComplexAlias a2{std::make_pair("ccc", "ddd")}; |
| EXPECT_TRUE(a1 < a2); |
| |
| EXPECT_TRUE(a1.value() == PairOfStrings("aaa", "bbb")); |
| |
| // Note a caveat, an std::pair doesn't have an overload of operator<<, and it |
| // cannot be easily added since ADL rules would require it to be in the std |
| // namespace. So we can't print ComplexAlias. |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeKeysInStdUnorderedMap) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| std::unordered_map<FooAlias, std::string, typename FooAlias::Hasher> map; |
| |
| FooAlias k1(GetExampleValue<TypeParam>(0)); |
| FooAlias k2(GetExampleValue<TypeParam>(1)); |
| |
| map[k1] = "value1"; |
| map[k2] = "value2"; |
| |
| EXPECT_EQ(map[k1], "value1"); |
| EXPECT_EQ(map[k2], "value2"); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanBeKeysInStdMap) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| std::map<FooAlias, std::string> map; |
| |
| FooAlias k1(GetExampleValue<TypeParam>(0)); |
| FooAlias k2(GetExampleValue<TypeParam>(1)); |
| |
| map[k1] = "value1"; |
| map[k2] = "value2"; |
| |
| EXPECT_EQ(map[k1], "value1"); |
| EXPECT_EQ(map[k2], "value2"); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CanDifferentiateOverloads) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| using BarAlias = StrongAlias<class BarTag, TypeParam>; |
| class Scope { |
| public: |
| static std::string Overload(FooAlias) { return "FooAlias"; } |
| static std::string Overload(BarAlias) { return "BarAlias"; } |
| }; |
| EXPECT_EQ("FooAlias", Scope::Overload(FooAlias())); |
| EXPECT_EQ("BarAlias", Scope::Overload(BarAlias())); |
| } |
| |
| TEST(StrongAliasTest, EnsureConstexpr) { |
| using FooAlias = StrongAlias<class FooTag, int>; |
| using BarAlias = StrongAlias<class BarTag, std::string_view>; |
| |
| // Check constructors. |
| static constexpr FooAlias kZero{}; |
| static constexpr FooAlias kOne(1); |
| static constexpr BarAlias kHello("Hello"); |
| |
| // Check operator->. |
| static_assert(kHello->size() == 5, ""); |
| |
| // Check operator*. |
| static_assert(*kZero == 0, ""); |
| static_assert(*kOne == 1, ""); |
| static_assert(*kHello == "Hello", ""); |
| |
| // Check value(). |
| static_assert(kZero.value() == 0, ""); |
| static_assert(kOne.value() == 1, ""); |
| |
| // Check explicit conversions to underlying type. |
| static_assert(static_cast<int>(kZero) == 0, ""); |
| static_assert(static_cast<int>(kOne) == 1, ""); |
| |
| // Check comparison operations. |
| static_assert(kZero == kZero, ""); |
| static_assert(kZero != kOne, ""); |
| static_assert(kZero < kOne, ""); |
| static_assert(kZero <= kOne, ""); |
| static_assert(kOne > kZero, ""); |
| static_assert(kOne >= kZero, ""); |
| } |
| |
| // This next test is compile-time, and thus not in an actual TEST since it would |
| // just result in running an empty test. |
| void StreamOperatorExists() { |
| // Aliases of ints should be streamable because ints are streamable. |
| using StreamableAlias = StrongAlias<class IntTag, int>; |
| static_assert(internal::SupportsOstreamOperator<StreamableAlias>); |
| |
| // Aliases of a class which does not expose a stream operator should |
| // themselves not be streamable. |
| class Scope { |
| public: |
| Scope() = default; |
| }; |
| using NonStreamableAlias = StrongAlias<class ScopeTag, Scope>; |
| static_assert(!internal::SupportsOstreamOperator<NonStreamableAlias>); |
| } |
| |
| #if BUILDFLAG(ENABLE_BASE_TRACING) |
| TEST(StrongAliasTest, TracedValueSupport) { |
| using IntAlias = StrongAlias<class FooTag, int>; |
| EXPECT_EQ(perfetto::TracedValueToString(IntAlias(42)), "42"); |
| } |
| #endif // BUILDFLAG(ENABLE_BASE_TRACING) |
| |
| } // namespace base |