Introduce a more generic version of IdType for opaque aliases
This one works with strings and other complex types in addition to
integrals.
Bug: 954080
Change-Id: Ic9961e0ccffdce89d160a5b4d8cab9b7d94a100b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1617481
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Ćukasz Anforowicz <lukasza@chromium.org>
Commit-Queue: Maciej Pawlowski <mpawlowski@opera.com>
Cr-Commit-Position: refs/heads/master@{#662532}
diff --git a/base/util/type_safety/BUILD.gn b/base/util/type_safety/BUILD.gn
index 61ffb6d..011e577 100644
--- a/base/util/type_safety/BUILD.gn
+++ b/base/util/type_safety/BUILD.gn
@@ -10,6 +10,7 @@
source_set("type_safety") {
sources = [
"id_type.h",
+ "strong_alias.h",
]
}
@@ -17,6 +18,7 @@
testonly = true
sources = [
"id_type_unittest.cc",
+ "strong_alias_unittest.cc",
]
deps = [
diff --git a/base/util/type_safety/id_type.h b/base/util/type_safety/id_type.h
index 2563139..29d1eb8e 100644
--- a/base/util/type_safety/id_type.h
+++ b/base/util/type_safety/id_type.h
@@ -5,11 +5,14 @@
#ifndef BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
#define BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
-#include <stdint.h>
-#include <cstddef>
-#include <ostream>
-#include <type_traits>
+#include <cstdint>
+#include "base/util/type_safety/strong_alias.h"
+
+namespace util {
+
+// A specialization of StrongAlias for integer-based identifiers.
+//
// IdType32<>, IdType64<>, etc. wrap an integer id in a custom, type-safe type.
//
// IdType32<Foo> is an alternative to int, for a class Foo with methods like:
@@ -38,72 +41,36 @@
// - it can be used in IPC messages.
//
// IdType32<Foo> has the following differences from a bare int32_t:
-// - it forces coercions to go through GetUnsafeValue and FromUnsafeValue;
+// - it forces coercions to go through the explicit constructor and value()
+// getter;
// - it restricts the set of available operations (i.e. no multiplication);
-// - it ensures initialization to zero and allows checking against
-// default-initialized values via is_null method.
-
-namespace util {
-
+// - it default-constructs to a null value and allows checking against the null
+// value via is_null method.
template <typename TypeMarker, typename WrappedType, WrappedType kInvalidValue>
-class IdType {
+class IdType : public StrongAlias<TypeMarker, WrappedType> {
public:
- IdType() : value_(kInvalidValue) {}
- bool is_null() const { return value_ == kInvalidValue; }
+ using StrongAlias<TypeMarker, WrappedType>::StrongAlias;
+ // Default-construct in the null state.
+ IdType() : StrongAlias<TypeMarker, WrappedType>::StrongAlias(kInvalidValue) {}
+ bool is_null() const { return this->value() == kInvalidValue; }
+
+ // TODO(mpawlowski) Replace these with constructor/value() getter. The
+ // conversions are safe as long as they're explicit (which is taken care of by
+ // StrongAlias).
static IdType FromUnsafeValue(WrappedType value) { return IdType(value); }
- WrappedType GetUnsafeValue() const { return value_; }
-
- IdType(const IdType& other) = default;
- IdType& operator=(const IdType& other) = default;
-
- bool operator==(const IdType& other) const { return value_ == other.value_; }
- bool operator!=(const IdType& other) const { return value_ != other.value_; }
- bool operator<(const IdType& other) const { return value_ < other.value_; }
- bool operator<=(const IdType& other) const { return value_ <= other.value_; }
-
- // Hasher to use in std::unordered_map, std::unordered_set, etc.
- struct Hasher {
- using argument_type = IdType;
- using result_type = std::size_t;
- result_type operator()(const argument_type& id) const {
- return std::hash<WrappedType>()(id.GetUnsafeValue());
- }
- };
-
- protected:
- explicit IdType(WrappedType val) : value_(val) {}
-
- private:
- // In theory WrappedType could be any type that supports ==, <, <<, std::hash,
- // etc., but to make things simpler (both for users and for maintainers) we
- // explicitly restrict the design space to integers. This means the users
- // can safely assume that IdType is relatively small and cheap to copy
- // and the maintainers don't have to worry about WrappedType being a complex
- // type (i.e. std::string or std::pair or a move-only type).
- using IntegralWrappedType =
- typename std::enable_if<std::is_integral<WrappedType>::value,
- WrappedType>::type;
- IntegralWrappedType value_;
+ WrappedType GetUnsafeValue() const { return this->value(); }
};
// Type aliases for convenience:
template <typename TypeMarker>
-using IdType32 = IdType<TypeMarker, int32_t, 0>;
+using IdType32 = IdType<TypeMarker, std::int32_t, 0>;
template <typename TypeMarker>
-using IdTypeU32 = IdType<TypeMarker, uint32_t, 0>;
+using IdTypeU32 = IdType<TypeMarker, std::uint32_t, 0>;
template <typename TypeMarker>
-using IdType64 = IdType<TypeMarker, int64_t, 0>;
+using IdType64 = IdType<TypeMarker, std::int64_t, 0>;
template <typename TypeMarker>
-using IdTypeU64 = IdType<TypeMarker, uint64_t, 0>;
-
-template <typename TypeMarker, typename WrappedType, WrappedType kInvalidValue>
-std::ostream& operator<<(
- std::ostream& stream,
- const IdType<TypeMarker, WrappedType, kInvalidValue>& id) {
- return stream << id.GetUnsafeValue();
-}
-
+using IdTypeU64 = IdType<TypeMarker, std::uint64_t, 0>;
} // namespace util
#endif // BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
diff --git a/base/util/type_safety/id_type_unittest.cc b/base/util/type_safety/id_type_unittest.cc
index 735426d..ba502f62 100644
--- a/base/util/type_safety/id_type_unittest.cc
+++ b/base/util/type_safety/id_type_unittest.cc
@@ -3,11 +3,6 @@
// found in the LICENSE file.
#include <limits>
-#include <map>
-#include <sstream>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
#include "base/util/type_safety/id_type.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -19,16 +14,6 @@
class Foo;
using FooId = IdType<Foo, int, 0>;
-class Bar;
-using BarId = IdType<Bar, int, 0>;
-
-class AnotherIdMarker;
-class DerivedId : public IdType<AnotherIdMarker, int, 0> {
- public:
- explicit DerivedId(int unsafe_value)
- : IdType<AnotherIdMarker, int, 0>(unsafe_value) {}
-};
-
} // namespace
TEST(IdType, DefaultValueIsInvalid) {
@@ -41,92 +26,6 @@
EXPECT_FALSE(foo_id.is_null());
}
-TEST(IdType, OutputStreamTest) {
- FooId foo_id = FooId::FromUnsafeValue(123);
-
- std::ostringstream ss;
- ss << foo_id;
- EXPECT_EQ("123", ss.str());
-}
-
-TEST(IdType, IdType32) {
- IdType32<Foo> id;
-
- EXPECT_EQ(0, id.GetUnsafeValue());
- static_assert(sizeof(int32_t) == sizeof(id), "");
-}
-
-TEST(IdType, IdTypeU32) {
- IdTypeU32<Foo> id;
-
- EXPECT_EQ(0u, id.GetUnsafeValue());
- static_assert(sizeof(uint32_t) == sizeof(id), "");
-}
-
-TEST(IdType, IdType64) {
- IdType64<Foo> id;
-
- EXPECT_EQ(0, id.GetUnsafeValue());
- static_assert(sizeof(int64_t) == sizeof(id), "");
-}
-
-TEST(IdType, IdTypeU64) {
- IdTypeU64<Foo> id;
-
- EXPECT_EQ(0u, id.GetUnsafeValue());
- static_assert(sizeof(uint64_t) == sizeof(id), "");
-}
-
-TEST(IdType, DerivedClasses) {
- DerivedId derived_id(456);
-
- std::ostringstream ss;
- ss << derived_id;
- EXPECT_EQ("456", ss.str());
-
- std::map<DerivedId, std::string> ordered_map;
- ordered_map[derived_id] = "blah";
- EXPECT_EQ(ordered_map[derived_id], "blah");
-
- std::unordered_map<DerivedId, std::string, DerivedId::Hasher> unordered_map;
- unordered_map[derived_id] = "blah2";
- EXPECT_EQ(unordered_map[derived_id], "blah2");
-}
-
-TEST(IdType, StaticAsserts) {
- static_assert(!std::is_constructible<FooId, int>::value,
- "Should be impossible to construct FooId from a raw integer.");
- static_assert(!std::is_convertible<int, FooId>::value,
- "Should be impossible to convert a raw integer into FooId.");
-
- static_assert(!std::is_constructible<FooId, BarId>::value,
- "Should be impossible to construct FooId from a BarId.");
- static_assert(!std::is_convertible<BarId, FooId>::value,
- "Should be impossible to convert a BarId into FooId.");
-
- // The presence of a custom default constructor means that FooId is not a
- // "trivial" class and therefore is not a POD type (unlike an int32_t).
- // At the same time FooId has almost all of the properties of a POD type:
- // - is "trivially copyable" (i.e. is memcpy-able),
- // - has "standard layout" (i.e. interops with things expecting C layout).
- // See http://stackoverflow.com/a/7189821 for more info about these
- // concepts.
- static_assert(std::is_standard_layout<FooId>::value,
- "FooId should have standard layout. "
- "See http://stackoverflow.com/a/7189821 for more info.");
- static_assert(sizeof(FooId) == sizeof(int),
- "FooId should be the same size as the raw integer it wraps.");
- // TODO(lukasza): Enable these once <type_traits> supports all the standard
- // C++11 equivalents (i.e. std::is_trivially_copyable instead of the
- // non-standard std::has_trivial_copy_assign).
- // static_assert(std::has_trivial_copy_constructor<FooId>::value,
- // "FooId should have a trivial copy constructor.");
- // static_assert(std::has_trivial_copy_assign<FooId>::value,
- // "FooId should have a trivial copy assignment operator.");
- // static_assert(std::has_trivial_destructor<FooId>::value,
- // "FooId should have a trivial destructor.");
-}
-
class IdTypeSpecificValueTest : public ::testing::TestWithParam<int> {
protected:
FooId test_id() { return FooId::FromUnsafeValue(GetParam()); }
@@ -139,17 +38,6 @@
}
};
-TEST_P(IdTypeSpecificValueTest, ComparisonToSelf) {
- EXPECT_TRUE(test_id() == test_id());
- EXPECT_FALSE(test_id() != test_id());
- EXPECT_FALSE(test_id() < test_id());
-}
-
-TEST_P(IdTypeSpecificValueTest, ComparisonToOther) {
- EXPECT_FALSE(test_id() == other_id());
- EXPECT_TRUE(test_id() != other_id());
-}
-
TEST_P(IdTypeSpecificValueTest, UnsafeValueRoundtrips) {
int original_value = GetParam();
FooId id = FooId::FromUnsafeValue(original_value);
@@ -157,37 +45,6 @@
EXPECT_EQ(original_value, final_value);
}
-TEST_P(IdTypeSpecificValueTest, Copying) {
- FooId original = test_id();
-
- FooId copy_via_constructor(original);
- EXPECT_EQ(original, copy_via_constructor);
-
- FooId copy_via_assignment;
- copy_via_assignment = original;
- EXPECT_EQ(original, copy_via_assignment);
-}
-
-TEST_P(IdTypeSpecificValueTest, StdUnorderedMap) {
- std::unordered_map<FooId, std::string, FooId::Hasher> map;
-
- map[test_id()] = "test_id";
- map[other_id()] = "other_id";
-
- EXPECT_EQ(map[test_id()], "test_id");
- EXPECT_EQ(map[other_id()], "other_id");
-}
-
-TEST_P(IdTypeSpecificValueTest, StdMap) {
- std::map<FooId, std::string> map;
-
- map[test_id()] = "test_id";
- map[other_id()] = "other_id";
-
- EXPECT_EQ(map[test_id()], "test_id");
- EXPECT_EQ(map[other_id()], "other_id");
-}
-
INSTANTIATE_TEST_SUITE_P(,
IdTypeSpecificValueTest,
::testing::Values(std::numeric_limits<int>::min(),
diff --git a/base/util/type_safety/strong_alias.h b/base/util/type_safety/strong_alias.h
new file mode 100644
index 0000000..7b0f38c
--- /dev/null
+++ b/base/util/type_safety/strong_alias.h
@@ -0,0 +1,116 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_UTIL_TYPE_SAFETY_STRONG_ALIAS_H_
+#define BASE_UTIL_TYPE_SAFETY_STRONG_ALIAS_H_
+
+#include <ostream>
+#include <utility>
+
+namespace util {
+
+// A type-safe alternative for a typedef or a 'using' directive.
+//
+// C++ currently does not support type-safe typedefs, despite multiple proposals
+// (ex. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf). The
+// next best thing is to try and emulate them in library code.
+//
+// The motivation is to disallow several classes of errors:
+//
+// using Orange = int;
+// using Apple = int;
+// Apple apple(2);
+// Orange orange = apple; // Orange should not be able to become an Apple.
+// Orange x = orange + apple; // Shouldn't add Oranges and Apples.
+// if (orange > apple); // Shouldn't compare Apples to Oranges.
+// void foo(Orange);
+// void foo(Apple); // Redefinition.
+// etc.
+//
+// StrongAlias may instead be used as follows:
+//
+// using Orange = StrongAlias<class OrangeTag, int>;
+// using Apple = StrongAlias<class AppleTag, int>;
+// Apple apple(2);
+// Orange orange = apple; // Does not compile.
+// Orange other_orange = orange; // Compiles, types match.
+// Orange x = orange + apple; // Does not compile.
+// Orange y = Orange(orange.value() + apple.value()); // Compiles.
+// if (orange > apple); // Does not compile.
+// if (orange > other_orange); // Compiles.
+// void foo(Orange);
+// void foo(Apple); // Compiles into separate overload.
+//
+// StrongAlias is a zero-cost abstraction, it's compiled away.
+//
+// TagType is an empty tag class (also called "phantom type") that only serves
+// the type system to differentiate between different instantiations of the
+// template.
+// UnderlyingType may be almost any value type. Note that some methods of the
+// StrongAlias may be unavailable (ie. produce elaborate compilation errors when
+// used) if UnderlyingType doesn't support them.
+//
+// StrongAlias only directly exposes comparison operators (for convenient use in
+// ordered containers) and a hash function (for unordered_map/set). It's
+// impossible, without reflection, to expose all methods of the UnderlyingType
+// in StrongAlias's interface. It's also potentially unwanted (ex. you don't
+// want to be able to add two StrongAliases that represent socket handles).
+// A getter is provided in case you need to access the UnderlyingType.
+template <typename TagType, typename UnderlyingType>
+class StrongAlias {
+ public:
+ StrongAlias() = default;
+ explicit StrongAlias(const UnderlyingType& v) : value_(v) {}
+ explicit StrongAlias(UnderlyingType&& v) : value_(std::move(v)) {}
+ ~StrongAlias() = default;
+
+ StrongAlias(const StrongAlias& other) = default;
+ StrongAlias& operator=(const StrongAlias& other) = default;
+ StrongAlias(StrongAlias&& other) = default;
+ StrongAlias& operator=(StrongAlias&& other) = default;
+
+ const UnderlyingType& value() const { return value_; }
+
+ bool operator==(const StrongAlias& other) const {
+ return value_ == other.value_;
+ }
+ bool operator!=(const StrongAlias& other) const {
+ return value_ != other.value_;
+ }
+ bool operator<(const StrongAlias& other) const {
+ return value_ < other.value_;
+ }
+ bool operator<=(const StrongAlias& other) const {
+ return value_ <= other.value_;
+ }
+ bool operator>(const StrongAlias& other) const {
+ return value_ > other.value_;
+ }
+ bool operator>=(const StrongAlias& other) const {
+ return value_ >= other.value_;
+ }
+
+ // Hasher to use in std::unordered_map, std::unordered_set, etc.
+ struct Hasher {
+ using argument_type = StrongAlias;
+ using result_type = std::size_t;
+ result_type operator()(const argument_type& id) const {
+ return std::hash<UnderlyingType>()(id.value());
+ }
+ };
+
+ protected:
+ UnderlyingType value_;
+};
+
+// Stream operator for convenience, streams the UnderlyingType.
+template <typename TagType, typename UnderlyingType>
+std::ostream& operator<<(std::ostream& stream,
+ const StrongAlias<TagType, UnderlyingType>& alias) {
+ return stream << alias.value();
+}
+
+} // namespace util
+
+#endif // BASE_UTIL_TYPE_SAFETY_STRONG_ALIAS_H_
diff --git a/base/util/type_safety/strong_alias_unittest.cc b/base/util/type_safety/strong_alias_unittest.cc
new file mode 100644
index 0000000..9b8af3a
--- /dev/null
+++ b/base/util/type_safety/strong_alias_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/util/type_safety/strong_alias.h"
+
+#include <cstdint>
+#include <map>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace util {
+
+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<typename std::remove_reference<decltype(
+ const_alias.value())>::type>::value,
+ "Reference returned by const value getter should be const.");
+}
+
+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)));
+}
+
+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, 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<FooAlias>::value,
+ "Should be possible to default-construct a StrongAlias.");
+}
+
+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_constructible<TypeParam, FooAlias>::value,
+ "Should be impossible to construct an underlying type from a "
+ "StrongAlias.");
+ 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:
+ 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()));
+}
+
+} // namespace util
diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h
index 53baea4..724539d 100644
--- a/ipc/ipc_message_utils.h
+++ b/ipc/ipc_message_utils.h
@@ -1055,6 +1055,26 @@
}
};
+template <typename TagType, typename UnderlyingType>
+struct ParamTraits<util::StrongAlias<TagType, UnderlyingType>> {
+ using param_type = util::StrongAlias<TagType, UnderlyingType>;
+ static void Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.value());
+ }
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ UnderlyingType value;
+ if (!ReadParam(m, iter, &value))
+ return false;
+ *r = param_type::StrongAlias(value);
+ return true;
+ }
+ static void Log(const param_type& p, std::string* l) {
+ LogParam(p.value(), l);
+ }
+};
+
// IPC types ParamTraits -------------------------------------------------------
// A ChannelHandle is basically a platform-inspecific wrapper around the