A system of expression templates is used to ensure something like a=b+c
turns into a simple call to mpz_add
etc. For mpf_class
the scheme also ensures the precision of the final
destination is used for any temporaries within a statement like
f=w*x+y*z
. These are important features which a naive implementation
cannot provide.
A simplified description of the scheme follows. The true scheme is complicated by the fact that expressions have different return types. For detailed information, refer to the source code.
To perform an operation, say, addition, we first define a “function object” evaluating it,
struct __gmp_binary_plus { static void eval(mpf_t f, const mpf_t g, const mpf_t h) { mpf_add(f, g, h); } };
And an “additive expression” object,
__gmp_expr<__gmp_binary_expr<mpf_class, mpf_class, __gmp_binary_plus> > operator+(const mpf_class &f, const mpf_class &g) { return __gmp_expr <__gmp_binary_expr<mpf_class, mpf_class, __gmp_binary_plus> >(f, g); }
The seemingly redundant __gmp_expr<__gmp_binary_expr<…>>
is used to
encapsulate any possible kind of expression into a single template type. In
fact even mpf_class
etc are typedef
specializations of
__gmp_expr
.
Next we define assignment of __gmp_expr
to mpf_class
.
template <class T> mpf_class & mpf_class::operator=(const __gmp_expr<T> &expr) { expr.eval(this->get_mpf_t(), this->precision()); return *this; } template <class Op> void __gmp_expr<__gmp_binary_expr<mpf_class, mpf_class, Op> >::eval (mpf_t f, mp_bitcnt_t precision) { Op::eval(f, expr.val1.get_mpf_t(), expr.val2.get_mpf_t()); }
where expr.val1
and expr.val2
are references to the expression’s
operands (here expr
is the __gmp_binary_expr
stored within the
__gmp_expr
).
This way, the expression is actually evaluated only at the time of assignment,
when the required precision (that of f
) is known. Furthermore the
target mpf_t
is now available, thus we can call mpf_add
directly
with f
as the output argument.
Compound expressions are handled by defining operators taking subexpressions as their arguments, like this:
template <class T, class U> __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, __gmp_binary_plus> > operator+(const __gmp_expr<T> &expr1, const __gmp_expr<U> &expr2) { return __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, __gmp_binary_plus> > (expr1, expr2); }
And the corresponding specializations of __gmp_expr::eval
:
template <class T, class U, class Op> void __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, Op> >::eval (mpf_t f, mp_bitcnt_t precision) { // declare two temporaries mpf_class temp1(expr.val1, precision), temp2(expr.val2, precision); Op::eval(f, temp1.get_mpf_t(), temp2.get_mpf_t()); }
The expression is thus recursively evaluated to any level of complexity and
all subexpressions are evaluated to the precision of f
.