| /* |
| * Copyright 2019 The Chromium Authors. All rights reserved. |
| * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| #include "rtc_base/strong_alias.h" |
| |
| #include <cstdint> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "rtc_base/containers/flat_map.h" |
| #include "rtc_base/gunit.h" |
| #include "test/gmock.h" |
| |
| // This is a copy of |
| // https://source.chromium.org/chromium/chromium/src/+/main:base/types/strong_alias_unittest.cc |
| // but adapted to use WebRTC's includes, remove unit tests that test the ostream |
| // operator (it's removed in this port) and other adaptations to pass lint. |
| |
| namespace webrtc { |
| 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); |
| } |
| |
| } // 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<typename std::remove_reference< |
| decltype(const_alias.value())>::type>::value, |
| "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<FooAlias>::value, |
| "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, 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<FooAlias>::value, |
| "Should be possible to default-construct a StrongAlias."); |
| static_assert( |
| std::is_trivially_default_constructible<FooAlias>::value == |
| std::is_trivially_default_constructible<TypeParam>::value, |
| "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<FooAlias>::value, |
| "int-based alias should have standard layout. "); |
| static_assert(std::is_trivially_copyable<FooAlias>::value, |
| "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<FooAlias, BarAlias>::value, |
| "Should be impossible to construct FooAlias from a BarAlias."); |
| static_assert(!std::is_convertible<BarAlias, FooAlias>::value, |
| "Should be impossible to convert a BarAlias into FooAlias."); |
| } |
| |
| TYPED_TEST(StrongAliasTest, CannotBeImplicitlyConverterToUnderlyingValue) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| static_assert(!std::is_convertible<FooAlias, TypeParam>::value, |
| "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: |
| explicit 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, CanBeKeysInFlatMap) { |
| using FooAlias = StrongAlias<class FooTag, TypeParam>; |
| webrtc::flat_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, 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>; |
| |
| // Check constructors. |
| static constexpr FooAlias kZero{}; |
| static constexpr FooAlias kOne(1); |
| |
| // Check operator*. |
| static_assert(*kZero == 0, ""); |
| static_assert(*kOne == 1, ""); |
| |
| // 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, ""); |
| } |
| |
| TEST(StrongAliasTest, BooleansAreEvaluatedAsBooleans) { |
| using BoolAlias = StrongAlias<class BoolTag, bool>; |
| |
| BoolAlias happy(true); |
| BoolAlias sad(false); |
| |
| EXPECT_TRUE(happy); |
| EXPECT_FALSE(sad); |
| EXPECT_TRUE(*happy); |
| EXPECT_FALSE(*sad); |
| } |
| } // namespace webrtc |