diff --git a/src/cpp-utils/value_type/ValueType.h b/src/cpp-utils/value_type/ValueType.h index 3644c177..16663dda 100644 --- a/src/cpp-utils/value_type/ValueType.h +++ b/src/cpp-utils/value_type/ValueType.h @@ -6,207 +6,204 @@ #include namespace cpputils { +namespace value_type { -namespace details { -// The Helper type is to allow the enable_if to depend on another type, say a local template type of the method. -// If enable_if depended only on class template parameters, it wouldn't work because they're already deduced when deducing the method. -template -using enable_if_t = std::enable_if_t::value && Condition, Type>; -} - -// TODO Test -template -class ValueType final { +/** +* This template simplifies generation of simple classes that wrap an id +* in a typesafe way. Namely, you can use it to create a very lightweight +* type that only offers equality comparators and hashing. Example: +* +* struct MyIdType final : IdValueType { +* constexpr explicit MyIdType(uint32_t id): IdValueType(id) {} +* }; +* +* Then in the global top level namespace: +* +* DEFINE_HASH_FOR_VALUE_TYPE(MyIdType); +* +* That's it - equality operators and hash functions are automatically defined +* for you, given the underlying type supports it. +* +* OrderedIdValueType: Use this instead of IdValueType if you need an ordering relation on your id type. +* This will define the operators +* - val < val +* - val > val +* - val <= val +* - val >= val +* +* QuantityValueType: Use this if you want a full-blown value type with arithmetics. +* Additionally to what OrderedIdValueType offers, this also defines: +* - ++val, val++ +* - --val, val-- +* - val += val (returns val) +* - val -= val (returns val) +* - val *= scalar (returns val) +* - val /= scalar (returns val) +* - val %= scalar (returns val) +* - val + val (returns val) +* - val - val (returns val) +* - val * scalar, scalar * val (returns val) +* - val / scalar (returns val) +* - val % scalar (returns val) +* - val / val (returns scalar) +* - val % val (returns scalar) +*/ +template +class IdValueType { public: - using underlying_type = typename Config::underlying_type; + using underlying_type = UnderlyingType; + using concrete_type = ConcreteType; - template - constexpr explicit ValueType(details::enable_if_t value) - : _value(value) {} + constexpr IdValueType(IdValueType&&) = default; + constexpr IdValueType(const IdValueType&) = default; + constexpr IdValueType& operator=(IdValueType&&) = default; + constexpr IdValueType& operator=(const IdValueType&) = default; - template - constexpr details::enable_if_t value() const { - return _value; +protected: + constexpr explicit IdValueType(underlying_type value) : value_(value) { + static_assert(std::is_base_of, ConcreteType>::value, + "CRTP violated. First template parameter of this class must be the concrete class."); + } + constexpr underlying_type& underlying_value() const { + return value_; } - template - constexpr details::enable_if_t operator++() { - ++_value; + friend struct std::hash; + + friend constexpr bool operator==(ConcreteType lhs, ConcreteType rhs) { + return lhs.value_ == rhs.value_; + } + + friend constexpr bool operator!=(ConcreteType lhs, ConcreteType rhs) { + return !operator==(lhs, rhs); + } + + underlying_type value_; +}; + +#define DEFINE_HASH_FOR_VALUE_TYPE(ClassName) \ + namespace std { \ + template <> \ + struct hash { \ + size_t operator()(ClassName x) const { \ + return std::hash()(x.value_); \ + } \ + }; \ + } + + +template +class OrderedIdValueType : public IdValueType { +protected: + using IdValueType::IdValueType; + + friend constexpr bool operator<(ConcreteType lhs, ConcreteType rhs) { + return lhs.value_ < rhs.value_; + } + + friend constexpr bool operator>(ConcreteType lhs, ConcreteType rhs) { + return lhs.value_ > rhs.value_; + } + + friend constexpr bool operator>=(ConcreteType lhs, ConcreteType rhs) { + return !operator<(lhs, rhs); + } + + friend constexpr bool operator<=(ConcreteType lhs, ConcreteType rhs) { + return !operator>(lhs, rhs); + } +}; + + +template +class QuantityValueType : public OrderedIdValueType { +protected: + using OrderedIdValueType::OrderedIdValueType; + +public: + constexpr ConcreteType& operator++() { + ++this->value_; return *this; } - template - constexpr details::enable_if_t operator++(int) { - ValueType tmp = *this; + constexpr ConcreteType operator++(int) { + ConcreteType tmp = *this; ++(*this); return tmp; } - template - constexpr details::enable_if_t operator--() { - --_value; + constexpr ConcreteType& operator--() { + --this->value_; return *this; } - template - constexpr details::enable_if_t operator--(int) { - ValueType tmp = *this; + constexpr ConcreteType operator--(int) { + ConcreteType tmp = *this; --(*this); return tmp; } - constexpr ValueType& operator+=(ValueType rhs); - constexpr ValueType& operator-=(ValueType rhs); - constexpr ValueType& operator*=(underlying_type rhs); - constexpr ValueType& operator/=(underlying_type rhs); - constexpr ValueType& operator%=(underlying_type rhs); + constexpr ConcreteType& operator+=(ConcreteType rhs) { + this->value_ += rhs.value_; + return *this; + } + + constexpr ConcreteType& operator-=(ConcreteType rhs) { + this->value_ -= rhs.value_; + return *this; + } + + constexpr ConcreteType& operator*=(UnderlyingType rhs) { + this->value_ *= rhs; + return *this; + } + + constexpr ConcreteType& operator/=(UnderlyingType rhs) { + this->value_ /= rhs; + return *this; + } + + constexpr ConcreteType& operator%=(UnderlyingType rhs) { + this->value_ %= rhs; + return *this; + } private: - friend struct std::hash; - underlying_type _value; + friend constexpr ConcreteType operator+(ConcreteType lhs, ConcreteType rhs) { + return lhs += rhs; + } + + friend constexpr ConcreteType operator-(ConcreteType lhs, ConcreteType rhs) { + return lhs -= rhs; + } + + friend constexpr ConcreteType operator*(ConcreteType lhs, UnderlyingType rhs) { + return lhs *= rhs; + } + + friend constexpr ConcreteType operator*(UnderlyingType lhs, ConcreteType rhs) { + return rhs * lhs; + } + + friend constexpr ConcreteType operator/(ConcreteType lhs, UnderlyingType rhs) { + return lhs /= rhs; + } + + friend constexpr UnderlyingType operator/(ConcreteType lhs, ConcreteType rhs) { + return lhs.value_ / rhs.value_; + } + + friend constexpr ConcreteType operator%(ConcreteType lhs, UnderlyingType rhs) { + return lhs %= rhs; + } + + friend constexpr UnderlyingType operator%(ConcreteType lhs, ConcreteType rhs) { + return lhs.value_ % rhs.value_; + } }; -/*template -constexpr ValueType operator "" _bytes(unsigned long long int value);*/ - -template constexpr ValueType operator+(ValueType lhs, ValueType rhs); -template constexpr ValueType operator-(ValueType lhs, ValueType rhs); -template constexpr ValueType operator*(ValueType lhs, typename Config::underlying_type rhs); -template constexpr ValueType operator*(typename Config::underlying_type lhs, ValueType rhs); -template constexpr ValueType operator/(ValueType lhs, typename Config::underlying_type rhs); -template constexpr typename Config::underlying_type operator/(ValueType lhs, ValueType rhs); -template constexpr ValueType operator%(ValueType lhs, typename Config::underlying_type rhs); -template constexpr typename Config::underlying_type operator%(ValueType lhs, ValueType rhs); - -template constexpr bool operator==(ValueType lhs, ValueType rhs); -template constexpr bool operator!=(ValueType lhs, ValueType rhs); -template constexpr bool operator<(ValueType lhs, ValueType rhs); -template constexpr bool operator>(ValueType lhs, ValueType rhs); -template constexpr bool operator<=(ValueType lhs, ValueType rhs); -template constexpr bool operator>=(ValueType lhs, ValueType rhs); - - -/* - * Implementation follows - */ - -/*template -inline constexpr ValueType operator "" _bytes(unsigned long long int value) { - return ValueType(value); -}*/ - - -template -inline constexpr ValueType& ValueType::operator+=(ValueType rhs) { - _value += rhs._value; - return *this; +} } -template -inline constexpr ValueType& ValueType::operator-=(ValueType rhs) { - _value -= rhs._value; - return *this; -} - -template -inline constexpr ValueType& ValueType::operator*=(typename Config::underlying_type rhs) { - _value *= rhs; - return *this; -} - -template -inline constexpr ValueType& ValueType::operator/=(typename Config::underlying_type rhs) { - _value /= rhs; - return *this; -} - -template -inline constexpr ValueType& ValueType::operator%=(typename Config::underlying_type rhs) { - _value %= rhs; - return *this; -} - -template -inline constexpr ValueType operator+(ValueType lhs, ValueType rhs) { - return lhs += rhs; -} - -template -inline constexpr ValueType operator-(ValueType lhs, ValueType rhs) { - return lhs -= rhs; -} - -template -inline constexpr ValueType operator*(ValueType lhs, typename Config::underlying_type rhs) { - return lhs *= rhs; -} - -template -inline constexpr ValueType operator*(typename Config::underlying_type lhs, ValueType rhs) { - return rhs * lhs; -} - -template -inline constexpr ValueType operator/(ValueType lhs, typename Config::underlying_type rhs) { - return lhs /= rhs; -} - -template -inline constexpr typename Config::underlying_type operator/(ValueType lhs, ValueType rhs) { - return lhs.value() / rhs.value(); -} - -template -inline constexpr ValueType operator%(ValueType lhs, typename Config::underlying_type rhs) { - return lhs %= rhs; -} - -template -inline constexpr typename Config::underlying_type operator%(ValueType lhs, ValueType rhs) { - return lhs.value() % rhs.value(); -} - - -template -inline constexpr bool operator==(ValueType lhs, ValueType rhs) { - return lhs.value() == rhs.value(); -} - -template -inline constexpr bool operator!=(ValueType lhs, ValueType rhs) { - return !operator==(lhs, rhs); -} - -template -inline constexpr bool operator<(ValueType lhs, ValueType rhs) { - return lhs.value() < rhs.value(); -} - -template -inline constexpr bool operator>(ValueType lhs, ValueType rhs) { - return lhs.value() > rhs.value(); -} - -template -inline constexpr bool operator<=(ValueType lhs, ValueType rhs) { - return !operator>(lhs, rhs); -} - -template -inline constexpr bool operator>=(ValueType lhs, ValueType rhs) { - return !operator<(lhs, rhs); -} - -} - -namespace std { - template - struct hash> { - constexpr hash() = default; - constexpr size_t operator()(cpputils::ValueType v) { - return v._value; - } - }; -} #endif diff --git a/test/cpp-utils/value_type/ValueTypeTest.cpp b/test/cpp-utils/value_type/ValueTypeTest.cpp index 5ca60550..1191cb3a 100644 --- a/test/cpp-utils/value_type/ValueTypeTest.cpp +++ b/test/cpp-utils/value_type/ValueTypeTest.cpp @@ -2,49 +2,169 @@ #include #include #include +#include +#include -using cpputils::valueType; +using cpputils::value_type::IdValueType; +using cpputils::value_type::OrderedIdValueType; +using cpputils::value_type::QuantityValueType; -struct Tag1{}; -using AllEnabledValueType = decltype(valueType() - .enable_explicit_value_constructor() - .enable_value_access() -)::type; +namespace { - -/* - * Test value() access - */ - -template struct has_value_access : std::false_type {}; -template struct has_value_access().value())>> : std::true_type { - using actual_result_type = std::result_of_t)(T*)>; - static_assert(std::is_same::value, "value() method returns wrong type"); +struct MyIdValueType : IdValueType { + constexpr MyIdValueType(int64_t val): IdValueType(val) {} }; -struct Tag2{}; -using ValueTypeWithValueAccess = decltype(valueType() - .enable_explicit_value_constructor() - .enable_value_access() -)::type; -struct Tag3{}; -using ValueTypeWithoutValueAccess = decltype(valueType() - .enable_explicit_value_constructor() -)::type; +struct MyOrderedIdValueType : OrderedIdValueType { + constexpr MyOrderedIdValueType(int64_t val): OrderedIdValueType(val) {} +}; -static_assert(has_value_access::value, ""); -static_assert(has_value_access::value, ""); -static_assert(!has_value_access::value, ""); +struct MyQuantityValueType : QuantityValueType { + constexpr MyQuantityValueType(int64_t val): QuantityValueType(val) {} +}; -TEST(ValueTypeTest, valueAccess){ - EXPECT_EQ(3, AllEnabledValueType(3).value()); - EXPECT_EQ(5.5, ValueTypeWithValueAccess(5.5).value()); +} +DEFINE_HASH_FOR_VALUE_TYPE(MyIdValueType); +DEFINE_HASH_FOR_VALUE_TYPE(MyOrderedIdValueType); +DEFINE_HASH_FOR_VALUE_TYPE(MyQuantityValueType); +namespace { + +template +struct IdValueTypeTest_constexpr_test { + static constexpr Type test_constructor = Type(5); + static_assert(Type(5) == test_constructor, ""); + + static constexpr Type test_copy_constructor = test_constructor; + static_assert(Type(5) == test_copy_constructor, ""); + + static constexpr Type test_copy_assignment = (Type(4) = 3); + static_assert(test_copy_assignment == Type(3), ""); + + static_assert(Type(5) == Type(5), ""); + static_assert(Type(5) != Type(6), ""); + + static constexpr bool success = true; +}; +static_assert(IdValueTypeTest_constexpr_test::success, ""); +static_assert(IdValueTypeTest_constexpr_test::success, ""); +static_assert(IdValueTypeTest_constexpr_test::success, ""); + + +template class IdValueTypeTest : public testing::Test { +}; +using IdValueTypeTest_types = testing::Types; +TYPED_TEST_CASE(IdValueTypeTest, IdValueTypeTest_types); + + +TYPED_TEST(IdValueTypeTest, Equality) { + TypeParam obj1(4); + TypeParam obj2(4); + TypeParam obj3(5); + + EXPECT_TRUE(obj1 == obj2); + EXPECT_TRUE(obj2 == obj1);; + EXPECT_FALSE(obj1 == obj3); + EXPECT_FALSE(obj3 == obj1); + + EXPECT_FALSE(obj1 != obj2); + EXPECT_FALSE(obj2 != obj1); + EXPECT_TRUE(obj1 != obj3); + EXPECT_TRUE(obj3 != obj1); } -struct Tag4{}; -using ValueTypeWithIncrementAndDecrement = decltype(valueType() - .enable_explicit_value_constructor() - .enable_value_access() - .enable_increment_and_decrement_operators() -)::type; -// TODO Test incrementAndDecrement \ No newline at end of file +TYPED_TEST(IdValueTypeTest, Constructor) { + TypeParam obj(4); + EXPECT_TRUE(obj == TypeParam(4)); +} + +TYPED_TEST(IdValueTypeTest, CopyConstructor) { + TypeParam obj(2); + TypeParam obj2(obj); + EXPECT_TRUE(obj2 == TypeParam(2)); + EXPECT_TRUE(obj == obj2); +} + +TYPED_TEST(IdValueTypeTest, MoveConstructor) { + TypeParam obj(2); + TypeParam obj2(std::move(obj)); + EXPECT_TRUE(obj2 == TypeParam(2)); +} + +TYPED_TEST(IdValueTypeTest, CopyAssignment) { + TypeParam obj(3); + TypeParam obj2(2); + obj2 = obj; + EXPECT_TRUE(obj2 == TypeParam(3)); + EXPECT_TRUE(obj == obj2); +} + +TYPED_TEST(IdValueTypeTest, MoveAssignment) { + TypeParam obj(3); + TypeParam obj2(2); + obj2 = std::move(obj); + EXPECT_TRUE(obj2 == TypeParam(3)); +} + +TYPED_TEST(IdValueTypeTest, Hash) { + TypeParam obj(3); + TypeParam obj2(3); + EXPECT_EQ(std::hash()(obj), std::hash()(obj2)); +} + +TYPED_TEST(IdValueTypeTest, UnorderedSet) { + std::unordered_set set; + set.insert(TypeParam(3)); + EXPECT_EQ(1u, set.count(TypeParam(3))); +} + + + + + + + +template +struct OrderedIdValueTypeTest_constexpr_test { + // TODO + + static constexpr bool success = true; +}; +static_assert(OrderedIdValueTypeTest_constexpr_test::success, ""); +static_assert(OrderedIdValueTypeTest_constexpr_test::success, ""); + +template class OrderedIdValueTypeTest : public testing::Test {}; +using OrderedIdValueTypeTest_types = testing::Types; +TYPED_TEST_CASE(OrderedIdValueTypeTest, OrderedIdValueTypeTest_types); + +// TODO Test cases for OrderedIdValueTypeTest + +TYPED_TEST(OrderedIdValueTypeTest, Set) { + std::set set; + set.insert(TypeParam(3)); + EXPECT_EQ(1u, set.count(TypeParam(3))); +} + + + + + + + + + + +template +struct QuantityValueTypeTest_constexpr_test { + // TODO + + static constexpr bool success = true; +}; +static_assert(QuantityValueTypeTest_constexpr_test::success, ""); + +template class QuantityValueTypeTest : public testing::Test {}; +using QuantityValueTypeTest_types = testing::Types; +TYPED_TEST_CASE(QuantityValueTypeTest, QuantityValueTypeTest_types); + +// TODO Test cases for QuantityValueTypeTest + +}