C++ Expression Templates | Lazy Evaluation Techniques
이 글의 핵심
A practical guide to expression templates: from motivation to vector/matrix examples, pitfalls, and library references.
The problem
// Simple vector
class Vector {
vector<double> data;
public:
Vector(size_t n) : data(n) {}
Vector operator+(const Vector& other) const {
Vector result(data.size());
for (size_t i = 0; i < data.size(); i++) {
result.data[i] = data[i] + other.data[i];
}
return result; // temporary
}
};
// Issue: three temporaries
Vector a, b, c, d;
Vector result = a + b + c + d;
// temp1 = a + b
// temp2 = temp1 + c
// temp3 = temp2 + d
// result = temp3
Expression-template approach
// Expression template
template<typename E>
class VecExpression {
public:
double operator const {
return static_cast<const E&>(*this)[i];
}
size_t size() const {
return static_cast<const E&>(*this).size();
}
};
// Concrete vector
class Vector : public VecExpression<Vector> {
vector<double> data;
public:
Vector(size_t n) : data(n) {}
double operator const { return data[i]; }
double& operator { return data[i]; }
size_t size() const { return data.size(); }
// Construct from an expression
template<typename E>
Vector(const VecExpression<E>& expr) : data(expr.size()) {
for (size_t i = 0; i < expr.size(); i++) {
data[i] = expr[i]; // lazy evaluation happens here
}
}
};
// Addition expression
template<typename E1, typename E2>
class VecAdd : public VecExpression<VecAdd<E1, E2>> {
const E1& u;
const E2& v;
public:
VecAdd(const E1& u, const E2& v) : u(u), v(v) {}
double operator const {
return u[i] + v[i];
}
size_t size() const { return u.size(); }
};
// Operator+
template<typename E1, typename E2>
VecAdd<E1, E2> operator+(const VecExpression<E1>& u, const VecExpression<E2>& v) {
return VecAdd<E1, E2>(static_cast<const E1&>(u), static_cast<const E2&>(v));
}
int main() {
Vector a(3), b(3), c(3);
// No temporaries for intermediate vectors
Vector result = a + b + c; // evaluated in one pass into result
}
Practical examples
Example 1: Vector ops
Includes VecScale, operator* with a scalar, and a dot helper—same as the Korean article.
Example 2: Matrix ops
MatExpression, Matrix, MatAdd, operator+—same structure as the Korean article.
Example 3: Lazy list map
ListExpression, List, ListMap, map helper—same as the Korean article.
Performance comparison
Benchmark sketch comparing naive chained + vs expression-template assignment—measure on your hardware; results vary.
Common pitfalls
Dangling references
Storing auto expr = a + b can dangle if operands are temporaries—evaluate into a concrete Vector promptly.
Long template errors
Add static_assert constraints where helpful.
Compile-time explosion
Break expressions into named temporaries when builds get too heavy.
Libraries
Eigen and Blaze apply expression templates (or similar) internally—see their documentation.
FAQ
Q1: When? Numeric kernels, matrix/vector code, internal DSLs.
Q2: Speedups? Often from removing temporaries—measure.
Q3: Downsides? Complexity, build time, diagnostics.
Q4: Implement yourself? Prefer mature libraries unless you have a narrow, controlled use case.
Q5: Debugging? Small tests, static_assert, disable aggressive optimizations while investigating.
Q6: Resources? C++ Templates: The Complete Guide, Eigen sources, Modern C++ Design.
Related posts
- Expression templates (deep dive)
- Type traits
- Tag dispatching
Practical tips
Debugging
- Fix warnings first; minimize reproducers.
Performance
- Profile before optimizing; define metrics.
Code review
- Align with team conventions.
Checklist
Before coding
- Best tool for the job?
- Maintainable by the team?
- Meets performance goals?
While coding
- Warnings cleared?
- Edge cases covered?
- Errors handled?
At review
- Clear intent?
- Enough tests?
- Documented?
Keywords
C++, expression template, metaprogramming, optimization, templates.
See also
- Expression templates (deep dive)
- SFINAE & Concepts
- Tag dispatching
- Type traits
- Alignment & padding