Reputation: 131976
Suppose my code has
using foo = int32_t;
somewhere, and
using bar = int32_t;
then, for some reason, I need to distinguish between this type and other int32_t
s (and their aliases). But - I still want them to behave just like int32_t
s.
Now, I can write:
struct foo { int32_t value; }
struct bar { int32_t value; }
which distinguishes between the types. But - these structs don't behave like int
s at all; I can't even compare foo
s to each others! (Well, not before C++20 anyway)
Since int32_t
is not a class, I can't do:
struct foo : public int32_t { }
although that would give me exactly what I need.
So it seems what I want to achieve is a "boxing" (a-la Java, C# etc.) of plain integers into classes, and the rest will be taken care of by inheritance.
It's certainly possible to blurt out a lot of boilerplate and implement all of the relevant operators for integers: assignment, comparison, arithmetic, etc. But, you know, DRY!
If I could override operator dot, that could save me all the code, but that proposal is stuck and doesn't look like it'll go somewhere soon.
So is there something else I could leverage to avoid all of that boilerplate?
Upvotes: 0
Views: 605
Reputation: 131976
Several on-and-off-site comments (including @HenriMenke) have brought up the term "strong typedef"s. C++ typedefs are "weak" - they define indistinguishable aliases. A strong typedef of a new type T as type U would make T behave like U, while not having type U.
What you want to do is define two "strong typedefs" of foo
and of bar
as int's.
There are at least two common strong-typedef, uh, libraries I guess you could say:
type_safe
(repo on GitHub): Provides ts::integer<T>
strong typdef
, which is older and macro-basedUpvotes: 0
Reputation: 48635
There is one way I have tried (but not heavily tested) to avoid repeating the boilerplate. It uses templates to easily make a new type simply by supplying a different number as a template parameter. The resulting type can be type aliased to get rid of the ugly template definition:
namespace alt {
template<std::size_t TypeId, typename Number>
class typed_number
{
public:
explicit typed_number(Number n): n(n) {}
typed_number(typed_number const& tn): n(tn.n) {}
typed_number& operator= (typed_number const& tn) { this->n = tn.n; return *this; }
typed_number& operator+=(typed_number const& tn) { this->n += tn.n; return *this; }
typed_number& operator-=(typed_number const& tn) { this->n -= tn.n; return *this; }
typed_number& operator*=(typed_number const& tn) { this->n *= tn.n; return *this; }
typed_number& operator/=(typed_number const& tn) { this->n /= tn.n; return *this; }
explicit operator Number() const { return n; }
bool operator==(typed_number tn) const { return this->n == tn; }
bool operator!=(typed_number tn) const { return this->n != tn; }
bool operator<=(typed_number tn) const { return this->n <= tn; }
bool operator>=(typed_number tn) const { return this->n >= tn; }
bool operator< (typed_number tn) const { return this->n < tn; }
bool operator> (typed_number tn) const { return this->n > tn; }
typed_number operator+(typed_number const& tn) const { return typed_number(this->n + tn.n); }
typed_number operator-(typed_number const& tn) const { return typed_number(this->n - tn.n); }
typed_number operator*(typed_number const& tn) const { return typed_number(this->n * tn.n); }
typed_number operator/(typed_number const& tn) const { return typed_number(this->n / tn.n); }
friend std::ostream& operator<<(std::ostream& os, typed_number<TypeId, Number> n)
{ return os << n.n; }
friend std::istream& operator>>(std::istream& is, typed_number<TypeId, Number>& n)
{ return is >> n.n; }
private:
Number n;
};
} // namespace alt
// give each incompatible type a different index
using dollars = alt::typed_number<0, int>;
using cents = alt::typed_number<1, int>;
int main()
{
auto d1 = dollars(5);
auto d2 = dollars(9);
auto d3 = d1 + d2;
std::cout << d1 << " + " << d2 << " = " << d3 << '\n';
}
You create the boilerplate once as a template class and instantiate it as different types merely by supplying a unique index as the first template parameter.
Upvotes: 2