Commit c00800ee authored by Maciej Pawlowski's avatar Maciej Pawlowski Committed by Commit Bot

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/+/1617481Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarŁukasz Anforowicz <lukasza@chromium.org>
Commit-Queue: Maciej Pawlowski <mpawlowski@opera.com>
Cr-Commit-Position: refs/heads/master@{#662532}
parent 71e356e2
...@@ -10,6 +10,7 @@ import("//build/config/jumbo.gni") ...@@ -10,6 +10,7 @@ import("//build/config/jumbo.gni")
source_set("type_safety") { source_set("type_safety") {
sources = [ sources = [
"id_type.h", "id_type.h",
"strong_alias.h",
] ]
} }
...@@ -17,6 +18,7 @@ source_set("tests") { ...@@ -17,6 +18,7 @@ source_set("tests") {
testonly = true testonly = true
sources = [ sources = [
"id_type_unittest.cc", "id_type_unittest.cc",
"strong_alias_unittest.cc",
] ]
deps = [ deps = [
......
...@@ -5,11 +5,14 @@ ...@@ -5,11 +5,14 @@
#ifndef BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_ #ifndef BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
#define BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_ #define BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
#include <stdint.h> #include <cstdint>
#include <cstddef>
#include <ostream>
#include <type_traits>
#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<>, 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: // IdType32<Foo> is an alternative to int, for a class Foo with methods like:
...@@ -38,72 +41,36 @@ ...@@ -38,72 +41,36 @@
// - it can be used in IPC messages. // - it can be used in IPC messages.
// //
// IdType32<Foo> has the following differences from a bare int32_t: // 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 restricts the set of available operations (i.e. no multiplication);
// - it ensures initialization to zero and allows checking against // - it default-constructs to a null value and allows checking against the null
// default-initialized values via is_null method. // value via is_null method.
namespace util {
template <typename TypeMarker, typename WrappedType, WrappedType kInvalidValue> template <typename TypeMarker, typename WrappedType, WrappedType kInvalidValue>
class IdType { class IdType : public StrongAlias<TypeMarker, WrappedType> {
public: public:
IdType() : value_(kInvalidValue) {} using StrongAlias<TypeMarker, WrappedType>::StrongAlias;
bool is_null() const { return value_ == kInvalidValue; } // Default-construct in the null state.
IdType() : StrongAlias<TypeMarker, WrappedType>::StrongAlias(kInvalidValue) {}
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: bool is_null() const { return this->value() == kInvalidValue; }
explicit IdType(WrappedType val) : value_(val) {}
private: // TODO(mpawlowski) Replace these with constructor/value() getter. The
// In theory WrappedType could be any type that supports ==, <, <<, std::hash, // conversions are safe as long as they're explicit (which is taken care of by
// etc., but to make things simpler (both for users and for maintainers) we // StrongAlias).
// explicitly restrict the design space to integers. This means the users static IdType FromUnsafeValue(WrappedType value) { return IdType(value); }
// can safely assume that IdType is relatively small and cheap to copy WrappedType GetUnsafeValue() const { return this->value(); }
// 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_;
}; };
// Type aliases for convenience: // Type aliases for convenience:
template <typename TypeMarker> template <typename TypeMarker>
using IdType32 = IdType<TypeMarker, int32_t, 0>; using IdType32 = IdType<TypeMarker, std::int32_t, 0>;
template <typename TypeMarker> template <typename TypeMarker>
using IdTypeU32 = IdType<TypeMarker, uint32_t, 0>; using IdTypeU32 = IdType<TypeMarker, std::uint32_t, 0>;
template <typename TypeMarker> template <typename TypeMarker>
using IdType64 = IdType<TypeMarker, int64_t, 0>; using IdType64 = IdType<TypeMarker, std::int64_t, 0>;
template <typename TypeMarker> template <typename TypeMarker>
using IdTypeU64 = IdType<TypeMarker, uint64_t, 0>; using IdTypeU64 = IdType<TypeMarker, std::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();
}
} // namespace util } // namespace util
#endif // BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_ #endif // BASE_UTIL_TYPE_SAFETY_ID_TYPE_H_
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
#include <limits> #include <limits>
#include <map>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
#include "base/util/type_safety/id_type.h" #include "base/util/type_safety/id_type.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -19,16 +14,6 @@ namespace { ...@@ -19,16 +14,6 @@ namespace {
class Foo; class Foo;
using FooId = IdType<Foo, int, 0>; 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 } // namespace
TEST(IdType, DefaultValueIsInvalid) { TEST(IdType, DefaultValueIsInvalid) {
...@@ -41,92 +26,6 @@ TEST(IdType, NormalValueIsValid) { ...@@ -41,92 +26,6 @@ TEST(IdType, NormalValueIsValid) {
EXPECT_FALSE(foo_id.is_null()); 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> { class IdTypeSpecificValueTest : public ::testing::TestWithParam<int> {
protected: protected:
FooId test_id() { return FooId::FromUnsafeValue(GetParam()); } FooId test_id() { return FooId::FromUnsafeValue(GetParam()); }
...@@ -139,17 +38,6 @@ class IdTypeSpecificValueTest : public ::testing::TestWithParam<int> { ...@@ -139,17 +38,6 @@ class IdTypeSpecificValueTest : public ::testing::TestWithParam<int> {
} }
}; };
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) { TEST_P(IdTypeSpecificValueTest, UnsafeValueRoundtrips) {
int original_value = GetParam(); int original_value = GetParam();
FooId id = FooId::FromUnsafeValue(original_value); FooId id = FooId::FromUnsafeValue(original_value);
...@@ -157,37 +45,6 @@ TEST_P(IdTypeSpecificValueTest, UnsafeValueRoundtrips) { ...@@ -157,37 +45,6 @@ TEST_P(IdTypeSpecificValueTest, UnsafeValueRoundtrips) {
EXPECT_EQ(original_value, final_value); 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(, INSTANTIATE_TEST_SUITE_P(,
IdTypeSpecificValueTest, IdTypeSpecificValueTest,
::testing::Values(std::numeric_limits<int>::min(), ::testing::Values(std::numeric_limits<int>::min(),
......
// 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_
// 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
...@@ -1055,6 +1055,26 @@ struct ParamTraits<util::IdType<TypeMarker, WrappedType, kInvalidValue>> { ...@@ -1055,6 +1055,26 @@ struct ParamTraits<util::IdType<TypeMarker, WrappedType, kInvalidValue>> {
} }
}; };
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 ------------------------------------------------------- // IPC types ParamTraits -------------------------------------------------------
// A ChannelHandle is basically a platform-inspecific wrapper around the // A ChannelHandle is basically a platform-inspecific wrapper around the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment