Reputation: 10539
I am trying to implement functionality similar to boost/operators.
Here is what I have so far:
template<typename T, typename TAG>
struct strong_type{
explicit strong_type(T v) : v(v){}
T v;
};
template<typename T, typename TAG>
struct addition{
addition() = default;
using N = strong_type<T, TAG>;
friend N operator+(const N &a, const N &b){
return N{ a.v + b.v };
}
};
struct myint_tag{};
struct myint :
strong_type<int, myint_tag>,
addition<int, myint_tag>{
using strong_type<int, myint_tag>::strong_type;
myint(const strong_type &other) : strong_type(v){}
};
int main(){
myint a{ 2 };
myint b{ 3 };
// result is not myint, but strong_type<int, myint_tag>
myint c = a + b;
}
However I don't see how this can be implemented without #define.
Is there a way to implement this without need to write myint(const strong_type &other)
?
Upvotes: 0
Views: 72
Reputation: 10539
Yakk - Adam Nevraumont is definitely very good,
but I found much easier and clear way to do the same.
If you think a bit, all these addition
/ strong_add
classes, need to inject "global" operators.
The operators itself do not need to be friend, but friend
keyword is used, because else the operator will be not injected into "global" space.
Also no one needs the struct. It is used only to inject the operators, so it might be empty struct.
template<typename T, typename TAG>
struct strong_type{
using type = T;
T v;
explicit constexpr strong_type(const T &v) : v(v) {}
};
Then surprisingly:
template<class V>
struct arithmetic{
friend constexpr V operator+ (const V &a, const V &b){ return { a.v + b.v }; }
friend constexpr V operator- (const V &a, const V &b){ return { a.v - b.v }; }
friend constexpr V operator* (const V &a, const V &b){ return { a.v * b.v }; }
friend constexpr V operator/ (const V &a, const V &b){ return { a.v / b.v }; }
friend V &operator+=(V &a, const V &b){ a.v += b.v; return a; }
friend V &operator-=(V &a, const V &b){ a.v -= b.v; return a; }
friend V &operator*=(V &a, const V &b){ a.v *= b.v; return a; }
friend V &operator/=(V &a, const V &b){ a.v /= b.v; return a; }
};
and finally:
struct myint_tag{};
struct myint : strong_type<int, myint_tag>,
arithmetic <myint>
{
using strong_type::strong_type;
};
int main(){
constexpr myint a{ 2 };
constexpr myint b{ 3 };
myint x{ 3 };
myint y{ 5 };
x = x + y * x;
}
Upvotes: 0
Reputation: 275893
template<class D>
struct strong_add {
friend D operator+( D lhs, D const& rhs ) {
lhs += rhs; return lhs;
}
friend D& operator+=( D& lhs, D const& rhs ) {
lhs.v += rhs.v;
return lhs;
}
};
struct myint :
strong_type<int, myint_tag>,
strong_add<myint> {
using strong_type<int, myint_tag>::strong_type;
};
This uses the CRTP. +
takes the lhs argument by value, because if you have cheap-to-move expensive-to-copy types like std::string
:
a + b + c + d + e
with a naive const&, const&
plus, we get a copy every +
, as we create a brand new object at each return point from the operator.
With a value, const&
plus, first a
is copied. Then we do += b
, then move the result, then += c
then move the result, then += e
then move the result. Only one copy is made.
We can go further if you want.
First we do this:
template<class T>
class detect_strong_type {
template<class X, class Tag>
static std::true_type tester( strong_type<X, Tag>const* );
static std::false_type tester( void* );
public:
using type=decltype( tester( (T*)nullptr ) );
};
template<class T>
using is_strong_type = typename detect_strong_type<T>::type;
enum class operators {
add, subtract, multiply, divide
};
template<operators o>
using op_tag_t = std::integral_constant<operators, o>;
template<operators o>
constexpr op_tag_t<o> op_tag{};
auto default_op( op_tag_t<operators::add> ) { return [](auto& lhs, auto const& rhs){ lhs += rhs; }; }
auto default_op( op_tag_t<operators::subtract> ) { return [](auto& lhs, auto const& rhs){ lhs -= rhs; }; }
auto default_op( op_tag_t<operators::multiply> ) { return [](auto& lhs, auto const& rhs){ lhs *= rhs; }; }
auto default_op( op_tag_t<operators::divide> ) { return [](auto& lhs, auto const& rhs){ lhs /= rhs; }; }
template<operators op, class D, class...Skip>
void do_operator( op_tag_t<op>, D& lhs, D const& rhs, Skip&&... ) {
default_op( op_tag<op> )( lhs, rhs );
}
template<class D>
struct can_add {
friend D operator+( D lhs, D const& rhs ) {
lhs += rhs; return lhs;
}
friend D& operator+=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::add>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_subtract {
friend D operator-( D lhs, D const& rhs ) {
lhs -= rhs; return lhs;
}
friend D& operator-=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::subtract>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_multiply {
friend D operator*( D lhs, D const& rhs ) {
lhs *= rhs; return lhs;
}
friend D& operator*=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::multiply>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_divide {
friend D operator/( D lhs, D const& rhs ) {
lhs *= rhs; return lhs;
}
friend D& operator/=( D& lhs, D const& rhs ) {
do_operator( op_tag<operators::divide>, lhs, rhs );
return lhs;
}
};
template<class D>
struct can_math:
can_add<D>, can_multiply<D>, can_subtract<D>, can_divide<D>
{};
now we teach do_operator
about strong_type
:
template<operators op, class D,
std::enable_if_t< is_strong_type<D>{}, bool> =true
>
void do_operator( op_tag_t<op>, D& lhs, D const& rhs ) {
do_operator( op_tag<op>, lhs.v, rhs.v );
}
and this works:
struct myint :
strong_type<int, myint_tag>,
can_math<myint>
{
using strong_type<int, myint_tag>::strong_type;
};
int main(){
myint a{ 2 };
myint b{ 3 };
myint c = a*b + b - a;
}
Now this is a bit overkill just for strong operators. What it does let you do is:
struct some_type: can_add<some_type> {
std::vector<int> values;
friend void do_operator( op_tag_t<operators::add>, some_type& lhs, some_type const& rhs ) {
lhs.values.insert( lhs.values.end(), rhs.values.begin(), rhs.values.end() );
}
};
and now some_type + some_type
and some_type += some_type
are implemented.
Upvotes: 1