Defining a class that behaves exactly like an `int` This is an exercise to create a class that behaves exactly like an int.

Questions


Am I missing anything?
Or did I going about this about this the wrong way?
Is there anything I could improve on?
Are there any tricks that I could learn from this?
How about style; does the style look OK?


#include &lt;iostream&gt; // std::cout
#include &lt;utility&gt;  // std::move

class jd_int {
public:
    jd_int() = default;

    jd_int(int i)             : _i{i}                   { }
    jd_int(const jd_int&amp; jdi) : _i{jdi._i}              { }
    jd_int(jd_int&amp;&amp; jdi)      : _i{std::move(jdi._i)}   { }

    jd_int operator= (int i)             { _i = i; return *this;                 }
    jd_int operator= (double d)          { _i = d; return *this;                 }
    jd_int operator= (const jd_int&amp; jdi) { _i = jdi._i; return *this;            }
    jd_int operator= (jd_int&amp;&amp; jdi)      { _i = std::move(jdi._i); return *this; }

    ~jd_int() = default;

    operator bool()   { return !!_i;                    }
    operator int()    { return static_cast&lt;int&gt;(_i);    }
    operator double() { return static_cast&lt;double&gt;(_i); }

    jd_int operator+=(jd_int jdi) { return _i += jdi._i; }
    jd_int operator+ (jd_int jdi) { return _i +  jdi._i; }

    jd_int operator-=(jd_int jdi) { return _i -= jdi._i; }
    jd_int operator- (jd_int jdi) { return _i -  jdi._i; }

    jd_int operator*=(jd_int jdi) { return _i *= jdi._i; }
    jd_int operator* (jd_int jdi) { return _i *  jdi._i; }

    jd_int operator/=(jd_int jdi) { return _i /= jdi._i; }
    jd_int operator/ (jd_int jdi) { return _i /  jdi._i; }

    jd_int operator%=(jd_int jdi) { return _i %= jdi._i; }
    jd_int operator% (jd_int jdi) { return _i %  jdi._i; }

    jd_int operator++()    { return ++_i;                          }
    jd_int operator++(int) { jd_int tmp = *this; ++_i; return tmp; }

    jd_int operator--()    { return --_i;                          }
    jd_int operator--(int) { jd_int tmp = *this; --_i; return tmp; }

    friend bool operator&lt; (jd_int lhs, jd_int rhs);
    friend bool operator&gt; (jd_int lhs, jd_int rhs);
    friend bool operator&lt;=(jd_int lhs, jd_int rhs);
    friend bool operator&gt;=(jd_int lhs, jd_int rhs);
    friend bool operator==(jd_int lhs, jd_int rhs);
    friend bool operator!=(jd_int lhs, jd_int rhs);

private:
    int _i;

    friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const jd_int jdi);
    friend std::istream&amp; operator&gt;&gt;(std::istream&amp; is, jd_int jdi);
};

bool operator&lt; (jd_int lhs, jd_int rhs) { return (lhs._i &lt;  rhs._i); }
bool operator&gt; (jd_int lhs, jd_int rhs) { return (lhs._i &gt;  rhs._i); }
bool operator&lt;=(jd_int lhs, jd_int rhs) { return (lhs._i &lt;= rhs._i); }
bool operator&gt;=(jd_int lhs, jd_int rhs) { return (lhs._i &gt;= rhs._i); }
bool operator==(jd_int lhs, jd_int rhs) { return (lhs._i == rhs._i); }
bool operator!=(jd_int lhs, jd_int rhs) { return (lhs._i != rhs._i); }

std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const jd_int jdi) {
    os &lt;&lt; jdi._i;
    return os;
}

std::istream&amp; operator&gt;&gt;(std::istream&amp; is, jd_int jdi) {
    is &gt;&gt; jdi._i;
    return is;
}
```
I would make the bool operator explicit (and const)

The explicit will prevent the object being auto converted to bool in situations where you don't want it too. This may break with int type but is usually a better work match.

explicit operator bool() const  { return !!_i;                    }


I know how the !! works but it is obscure and a lot of people will raise an eyebrow. At least write a comment about it.



These methods should be const

operator int()    const { return static_cast&lt;int&gt;(_i);    }
operator double() const { return static_cast&lt;double&gt;(_i); }
//                ^^^^^


They do not change the state of the class.



All the assignment operator methods are defined wrong.

jd_int operator+=(jd_int jdi) { return _i += jdi._i; }

// Should be:

jd_int&amp; operator+=(jd_int const&amp; jdi) { _i += jdi._i; return *this; }


The op= is modifying the current object. Thus you should return a reference (not an object). Consequentially the return should return *this. There is no need to pass the input parameter by value (this could cause an unrequited copy). Rather pass the parameter by const reference to avoid this.



All the simple operator could be done better

jd_int operator+ (jd_int jdi) { return _i +  jdi._i; }

// Should be:

jd_int operator+ (jd_int const&amp; jdi) { return jd_int(*this) += idi; }


Pass the parameter by const reference. Then use the op-assignment to do the work.



You define all the operators as members of the class. Personally I would also do this. But there is an argument for making the free standing functions.

id_int + int  =&gt; works.             (int will be converted to id_int then addition done)
int + id_int  =&gt; fails to compile.


If you convert the above members into free standing functions then you will get either side to auto convert. 



The freestanding friend functions.

friend bool operator&lt; (jd_int lhs, jd_int rhs);
...
friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const jd_int jdi);
friend std::istream&amp; operator&gt;&gt;(std::istream&amp; is, jd_int jdi);


You declare them in the function and then define them later. Why? These are trivial functions define them inside the class. Splitting the declaration and definition does not provide any benefit.

Also pass the parameters by const reference when you can.

friend bool operator&lt; (jd_int const&amp; lhs, jd_int const&amp; rhs)         {return (lhs._i &lt;  rhs._i); }
...
friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, jd_int const&amp; jdi) {return os &lt;&lt; idi._i;}
friend std::istream&amp; operator&gt;&gt;(std::istream&amp; is, jd_int&amp; jdi)       {return is &gt;&gt; idi._i;}




I think you are missing a couple of operations (binary and and binary or sprint to mind).



I hate it when people use underscore as the first character of an identifier.

int _i;


There are some complex rules around its usage. Not everybody knows these rules exactly so something best avoided. Also why i why not value at least that is a bit more meaningful.