#include <iostream>

#define MAKE_OPERATOR(name, op)                                         \
  template <typename Lhs, typename Rhs>                                 \
  struct name##_ {                                                      \
    Lhs lhs; Rhs rhs;                                                   \
    friend std::ostream& operator<<(std::ostream& os, name##_ const& n) \
    { return os << '(' << n.lhs << #op << n.rhs << ')'; }               \
    friend auto eval(name##_ const& n) -> decltype(eval(n.lhs) op eval(n.rhs)) \
    { return eval(n.lhs) op eval(n.rhs); }                              \
  };                                                                    \
  template <typename Lhs, typename Rhs, typename Res=name##_<Lhs,Rhs> > \
  Res name(Lhs const& lhs, Rhs const& rhs) { return Res{lhs, rhs}; }

double eval(double d) { return d; }
MAKE_OPERATOR(prod, *)
MAKE_OPERATOR(sum,  +)
MAKE_OPERATOR(div,  /)

int main()
{
  auto a = prod(sum(sum(2, 3), 1), 5);
  std::cout << a << " = " << eval(a) << '\n';

  auto b = div(sum(28, 2), prod(5, 2));
  std::cout << b << " = " << eval(b) << '\n';
}

