hyperoperations in c++

3 min read Original article ↗

of course we're not done. who told you we were done!?

the real beauty of c++ is making the hard easy, the easy hard, and both cause unexpected template substitution errors. we want some convenient syntax for this, and anyone who says we're abusing how operators work be damned! the stl gets to cause unexpected behavior thanks to operator precedence not changing in an overload, so why shouldn't we?

since python uses ** as an exponentiation operator, we should use that as our syntax for exponentiation, too, just a little extended. c++, as you may know, does not have a ** operator, but it does have a unary * operator — pointer dereferencing — that's conveniently overloadable! so we'll just use that.

but, like, how? and how are we going to manage to chain them and get the right operator?

we'll start with some intermediate types, since you can't overload built-in operators and some operator overloads have to be members (they think they're sooooo darned special). so we'll pull a trick from java sorry, bjarne (pp. 22–23). and define our own numeric class for the right-hand side. we're also going to need some way to count how many asterisks we've typed — and we want to do that statically — which i've decided to do via an intermediate type that should disappear at runtime. so:

24template <typename last>
26struct next_op;
27
28struct integer {
29  constexpr const static int N = 2;   int i;
31
32    integer(int i) : i(i) {}
34
35    operator int() const { return i; }
37
38    next_op<integer> operator*();
40};
41
42integer operator""_i(unsigned long long i) { return integer(i); }

for the purposes of this shitpost, i have refrained from testing some of the more exotic operations you can perform on integers, such as addition. the conversion operator operator int should take care of all that for us.

anyway, that's integer defined. now, for next_op:

44template <typename last>
45struct next_op {
46  constexpr const static int N = last::N + 1;
47  const last& l;
48
49    next_op(const last& l) : l(l) {}
51
52    next_op<next_op<last>> operator*() { return next_op<next_op<last>>(*this); }
54
55    operator int() const { return l; }
57};
58
59next_op<integer> integer::operator*() { return next_op<integer>(*this); }

the objective here being that we can "dereference" integer some number of times, then perform a final "multiplication" to actually apply the hyperoperator. speaking of:

61template<typename last>
62int operator*(int a, next_op<last> b) {
63  return hyperop<decltype(b)::N>(a, b);
64}

that should do it. i refer you back to the the beginning of this post.