Expression templatesExpression templates are a C++ template metaprogramming technique that builds structures representing a computation at compile time, where expressions are evaluated only as needed to produce efficient code for the entire computation.[1] Expression templates thus allow programmers to bypass the normal order of evaluation of the C++ language and achieve optimizations such as loop fusion. Expression templates were invented independently by Todd Veldhuizen and David Vandevoorde;[2][3] it was Veldhuizen who gave them their name.[3] They are a popular technique for the implementation of linear algebra software.[1] Motivation and exampleConsider a library representing vectors and operations on them. One common mathematical operation is to add two vectors u and v, element-wise, to produce a new vector. The obvious C++ implementation of this operation would be an overloaded /// @brief class representing a mathematical 3D vector
class Vec : public std::array<double, 3> {
public:
using std::array<double, 3>::array;
// inherit constructor (C++11)
// see https://en.cppreference.com/w/cpp/language/using_declaration
};
/// @brief sum 'u' and 'v' into a new instance of Vec
Vec operator+(Vec const &u, Vec const &v) {
Vec sum;
for (size_t i = 0; i < u.size(); i++) {
sum[i] = u[i] + v[i];
}
return sum;
}
Users of this class can now write A problem with this approach is that more complicated expressions such as Delayed evaluation solves this problem, and can be implemented in C++ by letting Expression templates implement delayed evaluation using expression trees that only exist at compile time. Each assignment to a Example implementation of expression templates : An example implementation of expression templates looks like the following. A base class template <typename E>
class VecExpression {
public:
static constexpr bool is_leaf = false;
double operator[](size_t i) const {
// Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
return static_cast<E const&>(*this)[i];
}
size_t size() const { return static_cast<E const&>(*this).size(); }
};
The Boolean class Vec : public VecExpression<Vec> {
std::array<double, 3> elems;
public:
static constexpr bool is_leaf = true;
decltype(auto) operator[](size_t i) const { return elems[i]; }
decltype(auto) &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
// construct Vec using initializer list
Vec(std::initializer_list<double> init) {
std::copy(init.begin(), init.end(), elems.begin());
}
// A Vec can be constructed from any VecExpression, forcing its evaluation.
template <typename E>
Vec(VecExpression<E> const& expr) {
for (size_t i = 0; i != expr.size(); ++i) {
elems[i] = expr[i];
}
}
};
The sum of two template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
// cref if leaf, copy otherwise
typename std::conditional<E1::is_leaf, const E1&, const E1>::type _u;
typename std::conditional<E2::is_leaf, const E2&, const E2>::type _v;
public:
static constexpr bool is_leaf = false;
VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
decltype(auto) operator[](size_t i) const { return _u[i] + _v[i]; }
size_t size() const { return _v.size(); }
};
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}
With the above definitions, the expression VecSum<VecSum<Vec, Vec>, Vec>
so elems[i] = expr[i];
is effectively expanded (following the recursive definitions of elems[i] = a.elems[i] + b.elems[i] + c.elems[i];
with no temporary Basic Usage : int main() {
Vec v0 = {23.4, 12.5, 144.56};
Vec v1 = {67.12, 34.8, 90.34};
Vec v2 = {34.90, 111.9, 45.12};
// Following assignment will call the ctor of Vec which accept type of
// `VecExpression<E> const&`. Then expand the loop body to
// a.elems[i] + b.elems[i] + c.elems[i]
Vec sum_of_vec_type = v0 + v1 + v2;
for (size_t i = 0; i < sum_of_vec_type.size(); ++i)
std::cout << sum_of_vec_type[i] << std::endl;
// To avoid creating any extra storage, other than v0, v1, v2
// one can do the following (Tested with C++11 on GCC 5.3.0)
auto sum = v0 + v1 + v2;
for (size_t i = 0; i < sum.size(); ++i)
std::cout << sum[i] << std::endl;
// Observe that in this case typeid(sum) will be VecSum<VecSum<Vec, Vec>, Vec>
// and this chaining of operations can go on.
}
ApplicationsExpression templates have been found especially useful by the authors of libraries for linear algebra, that is, for dealing with vectors and matrices of numbers. Among libraries employing expression template are Dlib, Armadillo, Blaze,[5] Blitz++,[6] Boost uBLAS,[7] Eigen,[8] POOMA,[9] Stan Math Library,[10] and xtensor.[11] Expression templates can also accelerate C++ automatic differentiation implementations,[12] as demonstrated in the Adept library. Outside of vector math, the Spirit parser framework uses expression templates to represent formal grammars and compile these into parsers. See also
References
|
Portal di Ensiklopedia Dunia