Skip to content

Commit

Permalink
Merge pull request foonathan#75
Browse files Browse the repository at this point in the history
Support mixed operators with other strong typedefs and noncommutative mixed operators
  • Loading branch information
foonathan authored Apr 2, 2018
2 parents a306972 + c198661 commit 5275797
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 9 deletions.
61 changes: 52 additions & 9 deletions include/type_safe/strong_typedef.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,29 @@ namespace type_safe
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

template<class T, typename = void_t<>>
struct is_strong_typedef
: std::false_type {};

template<class T>
struct is_strong_typedef<T, void_t<decltype(type_safe::detail::underlying_type(std::declval<T>()))>>
: std::true_type {};

template <class StrongTypedef, typename = typename std::enable_if<is_strong_typedef<StrongTypedef>::value>::type>
constexpr auto forward_or_underlying(StrongTypedef&& type) noexcept -> decltype(get(forward<StrongTypedef>(type)))
{
return get(forward<StrongTypedef>(type));
}
template <class T>
constexpr typename std::enable_if<!is_strong_typedef<T>::value, T&&>::type
forward_or_underlying(T&& type) noexcept
{
return forward<T>(type);
}
} // namespace detail

/// \exclude
Expand Down Expand Up @@ -263,36 +286,46 @@ namespace type_safe
}

/// \exclude
#define TYPE_SAFE_DETAIL_MAKE_OP_MIXED(Op, Name, Result) \
#define TYPE_SAFE_DETAIL_MAKE_OP_STRONGTYPEDEF_OTHER(Op, Name, Result) \
/** \exclude */ \
template <class StrongTypedef, typename OtherArg, typename Other, \
typename = detail::enable_if_convertible_same<Other&&, OtherArg>> \
constexpr Result operator Op(const Name<StrongTypedef, OtherArg>& lhs, Other&& rhs) \
{ \
return Result(get(static_cast<const StrongTypedef&>(lhs)) Op detail::forward<Other>(rhs)); \
return Result(get(static_cast<const StrongTypedef&>(lhs)) \
Op detail::forward_or_underlying(detail::forward<Other>(rhs))); \
} \
/** \exclude */ \
template <class StrongTypedef, typename OtherArg, typename Other, \
typename = detail::enable_if_convertible_same<Other&&, OtherArg>> \
constexpr Result operator Op(Name<StrongTypedef, OtherArg>&& lhs, Other&& rhs) \
{ \
return Result(get(static_cast<StrongTypedef&&>(lhs)) Op detail::forward<Other>(rhs)); \
} \
return Result(get(static_cast<StrongTypedef&&>(lhs)) \
Op detail::forward_or_underlying(detail::forward<Other>(rhs))); \
}

#define TYPE_SAFE_DETAIL_MAKE_OP_OTHER_STRONGTYPEDEF(Op, Name, Result) \
/** \exclude */ \
template <class StrongTypedef, typename OtherArg, typename Other, \
typename = detail::enable_if_convertible_same<Other&&, OtherArg>> \
constexpr Result operator Op(Other&& lhs, const Name<StrongTypedef, OtherArg>& rhs) \
{ \
return Result(detail::forward<Other>(lhs) Op get(static_cast<const StrongTypedef&>(rhs))); \
return Result(detail::forward_or_underlying(detail::forward<Other>(lhs)) \
Op get(static_cast<const StrongTypedef&>(rhs))); \
} \
/** \exclude */ \
template <class StrongTypedef, typename OtherArg, typename Other, \
typename = detail::enable_if_convertible_same<Other&&, OtherArg>> \
constexpr Result operator Op(Other&& lhs, Name<StrongTypedef, OtherArg>&& rhs) \
{ \
return Result(detail::forward<Other>(lhs) Op get(static_cast<StrongTypedef&&>(rhs))); \
return Result(detail::forward_or_underlying(detail::forward<Other>(lhs)) \
Op get(static_cast<StrongTypedef&&>(rhs))); \
}

#define TYPE_SAFE_DETAIL_MAKE_OP_MIXED(Op, Name, Result) \
TYPE_SAFE_DETAIL_MAKE_OP_STRONGTYPEDEF_OTHER(Op, Name, Result) \
TYPE_SAFE_DETAIL_MAKE_OP_OTHER_STRONGTYPEDEF(Op, Name, Result)

/// \exclude
#define TYPE_SAFE_DETAIL_MAKE_OP_COMPOUND(Op, Name) \
/** \exclude */ \
Expand Down Expand Up @@ -357,7 +390,8 @@ namespace type_safe
TYPE_SAFE_CONSTEXPR14 StrongTypedef& operator Op(Name<StrongTypedef, OtherArg>& lhs, \
Other&& rhs) \
{ \
get(static_cast<StrongTypedef&>(lhs)) Op detail::forward<Other>(rhs); \
get(static_cast<StrongTypedef&>(lhs)) \
Op detail::forward_or_underlying(detail::forward<Other>(rhs)); \
return static_cast<StrongTypedef&>(lhs); \
} \
/** \exclude */ \
Expand All @@ -366,7 +400,8 @@ namespace type_safe
TYPE_SAFE_CONSTEXPR14 StrongTypedef&& operator Op(Name<StrongTypedef, OtherArg>&& lhs, \
Other&& rhs) \
{ \
get(static_cast<StrongTypedef&&>(lhs)) Op detail::forward<Other>(rhs); \
get(static_cast<StrongTypedef&&>(lhs)) \
Op detail::forward_or_underlying(detail::forward<Other>(rhs)); \
return static_cast<StrongTypedef&&>(lhs); \
}

Expand All @@ -383,7 +418,13 @@ namespace type_safe
{ \
}; \
TYPE_SAFE_DETAIL_MAKE_OP_MIXED(Op, mixed_##Name, StrongTypedef) \
TYPE_SAFE_DETAIL_MAKE_OP_COMPOUND_MIXED(Op## =, mixed_##Name)
TYPE_SAFE_DETAIL_MAKE_OP_COMPOUND_MIXED(Op## =, mixed_##Name) \
template <class StrongTypedef, typename Other> \
struct mixed_##Name##_noncommutative \
{ \
}; \
TYPE_SAFE_DETAIL_MAKE_OP_STRONGTYPEDEF_OTHER(Op, mixed_##Name##_noncommutative, StrongTypedef) \
TYPE_SAFE_DETAIL_MAKE_OP_COMPOUND_MIXED(Op## =, mixed_##Name##_noncommutative)

template <class StrongTypedef>
struct equality_comparison
Expand Down Expand Up @@ -726,6 +767,8 @@ namespace type_safe

#undef TYPE_SAFE_DETAIL_MAKE_OP
#undef TYPE_SAFE_DETAIL_MAKE_OP_MIXED
#undef TYPE_SAFE_DETAIL_MAKE_OP_STRONGTYPEDEF_OTHER
#undef TYPE_SAFE_DETAIL_MAKE_OP_OTHER_STRONGTYPEDEF
#undef TYPE_SAFE_DETAIL_MAKE_OP_COMPOUND
#undef TYPE_SAFE_DETAIL_MAKE_STRONG_TYPEDEF_OP
} // namespace strong_typedef_op
Expand Down
93 changes: 93 additions & 0 deletions test/strong_typedef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@

using namespace type_safe;

#define CREATE_IS_OPERATOR_CALLABLE_WITH_ARGS_CHECKER(Op, CheckerName) \
template <typename Arg1, typename Arg2, typename = void> \
struct CheckerName : std::false_type \
{ \
}; \
template <typename Arg1, typename Arg2> \
struct CheckerName< \
Arg1, Arg2, \
strong_typedef_op::detail::void_t<decltype(static_cast<Arg1>(std::declval<Arg1>()) \
Op static_cast<Arg2>(std::declval<Arg2>()))>> : std::true_type \
{ \
};

CREATE_IS_OPERATOR_CALLABLE_WITH_ARGS_CHECKER(+, is_operator_plus_callable_with)
CREATE_IS_OPERATOR_CALLABLE_WITH_ARGS_CHECKER(-, is_operator_minus_callable_with)
CREATE_IS_OPERATOR_CALLABLE_WITH_ARGS_CHECKER(/, is_division_callable_with)

TEST_CASE("strong_typedef")
{
// only check compilation here
Expand Down Expand Up @@ -227,6 +244,37 @@ TEST_CASE("strong_typedef")
b = 1 + b;
REQUIRE(static_cast<int>(b) == 3);
}
SECTION("addition with other strong_typedef")
{
struct type_a : strong_typedef<type_a, int>
{
using strong_typedef::strong_typedef;
};
struct type_b : strong_typedef<type_b, int>,
strong_typedef_op::mixed_addition<type_b, type_a>
{
using strong_typedef::strong_typedef;
};
type_a a(3);
type_b b(1);
b += a; // 4
b = b + a; // 7
b = a + b; // 10
REQUIRE(static_cast<int>(b) == 10);

struct type_c : strong_typedef<type_b, int>
{
};

static_assert(is_operator_plus_callable_with<type_b, type_a>::value,
"type_b supports addition with type_a");
static_assert(is_operator_plus_callable_with<type_a, type_b>::value,
"type_b supports commutative addition with type_a");
static_assert(!is_operator_plus_callable_with<type_b, int>::value,
"type_b support addition only with type_a, not with int");
static_assert(!is_operator_plus_callable_with<type_b, type_c>::value,
"type_b support addition only with type_a, not with other strong_typedefs");
}
SECTION("subtraction")
{
struct type : strong_typedef<type, int>,
Expand All @@ -248,6 +296,28 @@ TEST_CASE("strong_typedef")
b = 1 - b;
REQUIRE(static_cast<int>(b) == 3);
}
SECTION("subtraction noncommutative")
{
struct type : strong_typedef<type, int>,
strong_typedef_op::subtraction<type>,
strong_typedef_op::mixed_subtraction_noncommutative<type, int>
{
using strong_typedef::strong_typedef;
};

type a(0);
a -= type(1); // -1
a = a - type(1); // -2
a = type(1) - a; // 3
REQUIRE(static_cast<int>(a) == 3);

type b(0);
b -= 1;
b = b - 1;
REQUIRE(static_cast<int>(b) == -2);
static_assert(is_operator_minus_callable_with<type, int>::value, "");
static_assert(!is_operator_minus_callable_with<int, type>::value, "type is noncommutative");
}
SECTION("multiplication")
{
struct type : strong_typedef<type, int>,
Expand Down Expand Up @@ -290,6 +360,29 @@ TEST_CASE("strong_typedef")
b = 2 / b;
REQUIRE(static_cast<int>(b) == 1);
}
SECTION("division noncommutative")
{
struct type : strong_typedef<type, int>,
strong_typedef_op::division<type>,
strong_typedef_op::mixed_division_noncommutative<type, int>
{
using strong_typedef::strong_typedef;
};

type a(8);
a /= type(2);
a = a / type(2);
a = type(2) / a;
REQUIRE(static_cast<int>(a) == 1);

type b(8);
b /= 2;
b = b / 2;
REQUIRE(static_cast<int>(b) == 2);
static_assert(is_division_callable_with<type, int>::value, "");
static_assert(!is_division_callable_with<int, type>::value,
"division of type and int is noncommutative");
}
SECTION("modulo")
{
struct type : strong_typedef<type, int>,
Expand Down

0 comments on commit 5275797

Please sign in to comment.