diff --git a/base/base.pro b/base/base.pro index 49052f5b1a..d0976ea2f7 100644 --- a/base/base.pro +++ b/base/base.pro @@ -54,6 +54,7 @@ HEADERS += \ matrix.hpp \ mem_trie.hpp \ mutex.hpp \ + newtype.hpp \ object_tracker.hpp \ observer_list.hpp \ range_iterator.hpp \ diff --git a/base/base_tests/base_tests.pro b/base/base_tests/base_tests.pro index 7624cc052f..7fa3e5a31b 100644 --- a/base/base_tests/base_tests.pro +++ b/base/base_tests/base_tests.pro @@ -23,6 +23,7 @@ SOURCES += \ const_helper.cpp \ containers_test.cpp \ logging_test.cpp \ + newtype_test.cpp \ math_test.cpp \ matrix_test.cpp \ mem_trie_test.cpp \ diff --git a/base/base_tests/newtype_test.cpp b/base/base_tests/newtype_test.cpp new file mode 100644 index 0000000000..0a5741eb11 --- /dev/null +++ b/base/base_tests/newtype_test.cpp @@ -0,0 +1,99 @@ +#include "testing/testing.hpp" + +#include "base/newtype.hpp" + +#include "std/sstream.hpp" +#include "std/type_traits.hpp" + +namespace +{ +NEWTYPE(int, Int); + +string DebugPrint(Int const & i) +{ + stringstream sstr; + sstr << "Int(" << i.Get() << ')'; + return sstr.str(); +} + +UNIT_TEST(NewType_TypeChecks) +{ + TEST((is_constructible::value), ()); + TEST((is_constructible::value), ()); + TEST(!(is_convertible::value), ()); + TEST(!(is_convertible::value), ()); +} + +UNIT_TEST(NewType_Base) +{ + Int a{10}; + TEST_EQUAL(a.Get(), 10, ()); + + a.Set(100); + TEST_EQUAL(a.Get(), 100, ()); +} + +UNIT_TEST(NewType_Operations) +{ + TEST(Int(10) == Int(10), ()); + TEST(Int(20) != Int(30), ()); + TEST(Int(10) < Int(20), ()); + TEST(Int(10) <= Int(20), ()); + TEST(Int(20) > Int(10), ()); + TEST(Int(20) >= Int(10), ()); + + TEST_EQUAL(Int(10) + Int(20), Int(30), ()); + TEST_EQUAL(Int(10) - Int(20), Int(-10), ()); + TEST_EQUAL(Int(10) / Int(2), Int(5), ()); + TEST_EQUAL(Int(10) * Int(2), Int(20), ()); + TEST_EQUAL(Int(10) % Int(3), Int(1), ()); + + TEST_EQUAL(Int(10) | Int(7), Int(10 | 7), ()); + TEST_EQUAL(Int(10) & Int(7), Int(10 & 7), ()); + TEST_EQUAL(Int(10) ^ Int(7), Int(10 ^ 7), ()); +} + +UNIT_TEST(NewTypeMember_Operations) +{ + Int a(10); + auto b = a--; + TEST_EQUAL(a, Int(9), ()); + TEST_EQUAL(b, Int(10), ()); + + b = --a; + TEST_EQUAL(a, b, ()); + TEST_EQUAL(a, Int(8), ()); + + b = ++a; + TEST_EQUAL(a, b, ()); + TEST_EQUAL(a, Int(9), ()); + + b = a++; + TEST_EQUAL(a, Int(10), ()); + TEST_EQUAL(b, Int(9), ()); + + a.Set(100); + b = Int(2); + a *= b; + TEST_EQUAL(a, Int(200), ()); + + a /= b; + TEST_EQUAL(a, Int(100), ()); + + b.Set(3); + a %= b; + TEST_EQUAL(a, Int(1), ()); + + a.Set(10); + a |= Int(7); + TEST_EQUAL(a, Int(10 | 7), ()); + + a.Set(10); + a &= Int(7); + TEST_EQUAL(a, Int(10 & 7), ()); + + a.Set(10); + a ^= Int(7); + TEST_EQUAL(a, Int(10 ^ 7), ()); +} +} // namespace diff --git a/base/newtype.hpp b/base/newtype.hpp new file mode 100644 index 0000000000..0405e9866a --- /dev/null +++ b/base/newtype.hpp @@ -0,0 +1,142 @@ +#pragma once + +#include "std/type_traits.hpp" + +namespace my +{ +namespace impl +{ +template +using IsConvertibleGuard = typename enable_if::value>::type *; +} // namespace impl + +/// Creates a typesafe alias to a given numeric Type. +template +class NewType +{ + static_assert(is_integral::value || is_floating_point::value, + "NewType can be used only with integral and floating point type."); + +public: + template = nullptr> + explicit NewType(V const & v) : m_value(v) + { + } + + template = nullptr> + NewType & Set(V const & v) + { + m_value = static_cast(v); + return *this; + } + + Type const & Get() const { return m_value; } + Type & Get() { return m_value; } + + NewType & operator=(NewType const & v) + { + m_value = v.m_value; + return *this; + } + + NewType & operator++() + { + ++m_value; + return *this; + } + + NewType const operator++(int) + { + auto const copy = *this; + ++m_value; + return copy; + } + + NewType & operator--() + { + --m_value; + return *this; + } + + NewType const operator--(int) + { + auto const copy = *this; + --m_value; + return copy; + } + + NewType & operator+=(NewType const & v) + { + m_value += v.m_value; + return *this; + } + + NewType & operator-=(NewType const & v) + { + m_value -= v.m_value; + return *this; + } + + NewType & operator*=(NewType const & v) + { + m_value *= v.m_value; + return *this; + } + + NewType & operator/=(NewType const & v) + { + m_value /= v.m_value; + return *this; + } + + NewType & operator%=(NewType const & v) + { + m_value %= v.m_value; + return *this; + } + + NewType & operator^=(NewType const & v) + { + m_value ^= v.m_value; + return *this; + } + + NewType & operator|=(NewType const & v) + { + m_value |= v.m_value; + return *this; + } + + NewType & operator&=(NewType const & v) + { + m_value &= v.m_value; + return *this; + } + + // TODO(mgsergio): Is it meaningful for a newtype to have << operator ? + // NewType & operator<<=(NewType const & v) + // NewType & operator>>=(NewType const & v) + + bool operator==(NewType const & o) const { return m_value == o.m_value; } + bool operator!=(NewType const & o) const { return !(m_value == o.m_value); } + bool operator<(NewType const & o) const { return m_value < o.m_value; } + bool operator>(NewType const & o) const { return m_value > o.m_value; } + bool operator<=(NewType const & o) const { return !(m_value > o.m_value); } + bool operator>=(NewType const & o) const { return !(m_value < o.m_value); } + NewType operator+(NewType const & o) const { return NewType(m_value + o.m_value); } + NewType operator-(NewType const & o) const { return NewType(m_value - o.m_value); } + NewType operator*(NewType const & o) const { return NewType(m_value * o.m_value); } + NewType operator/(NewType const & o) const { return NewType(m_value / o.m_value); } + NewType operator%(NewType const & o) const { return NewType(m_value % o.m_value); } + NewType operator^(NewType const & o) const { return NewType(m_value ^ o.m_value); } + NewType operator|(NewType const & o) const { return NewType(m_value | o.m_value); } + NewType operator&(NewType const & o) const { return NewType(m_value & o.m_value); } + +private: + Type m_value; +}; +} // namespace my + +#define NEWTYPE(REPR, NAME) \ + struct NAME ## _tag; \ + using NAME = my::NewType diff --git a/std/type_traits.hpp b/std/type_traits.hpp index c292cd1df8..0a8c78401e 100644 --- a/std/type_traits.hpp +++ b/std/type_traits.hpp @@ -11,6 +11,8 @@ using std::conditional; using std::enable_if; using std::is_arithmetic; using std::is_base_of; +using std::is_constructible; +using std::is_convertible; using std::is_floating_point; using std::is_integral; using std::is_pod;