1.1 Basic Vector Class
#include
#include
#include
template<typename T>
class Vec {
std::vector<T> data;
public:
explicit Vec(size_t size) : data(size) {}
Vec(std::initializer_list<T> list) : data(list) {}
T operator[](size_t i) const { return data[i]; }
T& operator[](size_t i) { return data[i]; }
size_t size() const { return data.size(); }
// Assignment triggers evaluation
template<typename Expr>
Vec& operator=(const Expr& expr) {
for (size_t i=0; i<size(); ++i)
data[i] = expr[i];
return *this;
}
};
1.2 Expression Template for Addition
template<typename Lhs, typename Rhs>
class VecSum {
const Lhs& lhs;
const Rhs& rhs;
public:
VecSum(const Lhs& l, const Rhs& r) : lhs(l), rhs(r) {
assert(l.size() == r.size());
}
T operator[](size_t i) const {
return lhs[i] + rhs[i];
}
size_t size() const { return lhs.size(); }
};
// Operator + returns expression template
template<typename Lhs, typename Rhs>
VecSum<Lhs, Rhs> operator+(const Lhs& l, const Rhs& r) {
return VecSum<Lhs, Rhs>(l, r);
}
1.3 Testing Code
int main() {
Vec<double> A = {1, 2, 3};
Vec<double> B = {4, 5, 6};
Vec<double> C = {7, 8, 9};
Vec<double> D(3);
D = A + B + C; // Single loop execution
for (size_t i=0; i<D.size(); ++i)
std::cout << D[i] << " "; // Output: 12 15 18
return 0;
}
2.1 Supporting Scalar Multiplication
template<typename T>
class Scalar {
T value;
public:
Scalar(T v) : value(v) {}
T operator[](size_t) const { return value; }
size_t size() const { return 0; } // Not used
};
template<typename Lhs, typename Rhs>
class VecProd {
const Lhs& lhs;
const Rhs& rhs;
public:
VecProd(const Lhs& l, const Rhs& r) : lhs(l), rhs(r) {}
T operator[](size_t i) const {
return lhs[i] * rhs[i];
}
size_t size() const { return lhs.size(); }
};
// Mixed vector-scalar multiplication
template<typename Lhs, typename Rhs>
auto operator*(const Lhs& l, const Rhs& r) {
if constexpr (std::is_arithmetic_v<Lhs>)
return VecProd<Scalar<Lhs>, Rhs>(Scalar<Lhs>(l), r);
else if constexpr (std::is_arithmetic_v<Rhs>)
return VecProd<Lhs, Scalar<Rhs>>(l, Scalar<Rhs>(r));
else
return VecProd<Lhs, Rhs>(l, r);
}
2.2 Test Case with Multiplication
int main() {
Vec<double> A = {1, 2, 3};
Vec<double> B = {4, 5, 6};
auto expr = A * 2.0 + B * 3.0;
Vec<double> C(3);
C = expr;
for (size_t i=0; i<C.size(); ++i)
std::cout << C[i] << " "; // Output: 16 19 22
return 0;
}
3.1 Temporary Object Elimination
Traditional approach:
C = A + B; // Creates temporary VecSum, then copies to C
Expression Template approach:
// No temporaries - single loop in assignment
C.operator=<VecSum<Vec, Vec>>(A + B);
3.2 Loop Fusion Proof
Inspect assembly output (-S
flag):
What is the primary purpose of using Expression Templates in numerical computations?
A) To enable runtime polymorphism
B) To eliminate temporary objects during expression evaluation
C) To enable compile-time expression optimization
D) To simplify debugging
Correct Answers: B, C
Which C++ feature is critical for implementing Expression Templates?
A) Virtual functions
B) Operator overloading
C) Template metaprogramming
D) Exception handling
Correct Answers: B, C
Why must intermediate expression objects in Expression Templates be lightweight?
A) To reduce heap allocations
B) To avoid deep copies during expression traversal
C) To improve cache locality
D) To simplify type deduction
Correct Answers: A, B
What role does the Curiously Recurring Template Pattern (CRTP) play in Expression Templates?
A) Enables static polymorphism for expression nodes
B) Reduces virtual function overhead
C) Facilitates dynamic type checking
D) Automates memory management
Correct Answers: A, B
Which optimization is directly enabled by Expression Templates?
A) Loop unrolling
B) Lazy evaluation of subexpressions
C) Vectorization via SIMD instructions
D) Constant folding
Correct Answers: B, C
What is a key challenge when designing Expression Templates for multi-dimensional arrays?
A) Handling varying dimensions at runtime
B) Ensuring expression node compatibility across dimensions
C) Managing thread safety
D) Supporting irregular memory layouts
Correct Answers: A, B
Why is type erasure generally avoided in Expression Templates?
A) It introduces runtime overhead
B) It prevents compile-time optimizations
C) It complicates operator overloading
D) It increases code bloat
Correct Answers: A, B
Which C++17/20 feature significantly enhances Expression Template implementations?
A) constexpr
algorithms
B) Fold expressions
C) Concepts
D) Coroutines
Correct Answers: A, C
What is a common drawback of Expression Templates?
A) Increased compile-time complexity
B) Reduced debuggability due to template layers
C) Incompatibility with STL algorithms
D) Limited support for user-defined types
Correct Answers: A, B
How do Expression Templates interact with RVO (Return Value Optimization)?
A) They rely on RVO to eliminate copies
B) They bypass RVO through lazy evaluation
C) They conflict with RVO in nested expressions
D) They make RVO unnecessary
Correct Answers: B, D
1. Lazy Vector Arithmetic
Task: Implement a VecExpr
template to represent lazy vector operations (e.g., A + B * C
).
Requirements:
+
, -
, *
, /
Vector
classTest Case:
Vector<double> A{1, 2, 3}, B{4, 5, 6}, C{7, 8, 9};
Vector<double> D = A + B * C; // D[i] = A[i] + B[i] * C[i]
Solution Code:
#include
#include
template<typename Expr>
class VecExpression {
public:
double operator[](size_t i) const { return static_cast<const Expr&>(*this)[i]; }
};
class Vector : public VecExpression<Vector> {
std::vector<double> data;
public:
Vector(std::initializer_list<double> init) : data(init) {}
template<typename Expr>
Vector& operator=(const VecExpression<Expr>& expr) {
data.resize(sizeof(Expr)); // Simplified for demonstration
for(size_t i=0; i<data.size(); ++i) data[i] = expr[i];
return *this;
}
double operator[](size_t i) const { return data[i]; }
};
template<typename L, typename R>
class AddVec : public VecExpression<AddVec<L, R>> {
const L& lhs; const R& rhs;
public:
AddVec(const L& l, const R& r) : lhs(l), rhs(r) {}
double operator[](size_t i) const { return lhs[i] + rhs[i]; }
};
template<typename L, typename R>
AddVec<L, R> operator+(const VecExpression<L>& l, const VecExpression<R>& r) {
return {static_cast<const L&>(l), static_cast<const R&>(r)};
}
// Similar implementations for Subtract, Multiply, Divide...
int main() {
Vector A{1, 2, 3}, B{4, 5, 6}, C{7, 8, 9};
Vector D = A + B * C; // Implement Multiply similarly
for(size_t i=0; i<3; ++i) std::cout << D[i] << " "; // Output: 29 42 57
}
Key Points:
operator[]
.2. Matrix-Matrix Multiplication
Task: Design an Expression Template for matrix multiplication C = A * B
with lazy evaluation.
Requirements:
Test Case:
Matrix<float> A(2, 3, 1.0f), B(3, 2, 2.0f);
Matrix<float> C = A * B; // C[0][0] = 6.0f (sum of 1*2 + 1*2 + 1*2)
Solution Code:
template<typename T>
class Matrix {
std::vector<std::vector<T>> data;
public:
Matrix(size_t rows, size_t cols, T val) : data(rows, std::vector<T>(cols, val)) {}
T operator()(size_t i, size_t j) const { return data[i][j]; }
// ... Assignment operator similar to Vector example ...
};
template<typename L, typename R>
class MatMul {
const L& lhs; const R& rhs;
public:
MatMul(const L& l, const R& r) : lhs(l), rhs(r) {}
T operator()(size_t i, size_t j) const {
T sum = 0;
for(size_t k=0; k<lhs.cols(); ++k) sum += lhs(i, k) * rhs(k, j);
return sum;
}
};
template<typename L, typename R>
MatMul<L, R> operator*(const MatrixExpression<L>& l, const MatrixExpression<R>& r) {
return {static_cast<const L&>(l), static_cast<const R&>(r)};
}
Key Points:
MatMul
computes dot product on-demand during assignment.3. Scalar-Expression Integration
Task: Extend Expression Templates to support mixed scalar/vector operations like A = B * 2.5 + C
.
Requirements:
Test Case:
Vector<double> B{1, 2}, C{3, 4};
Vector<double> A = B * 2.5 + C; // A[0] = 1*2.5 + 3 = 5.5
Solution Code:
class Scalar {
double value;
public:
Scalar(double v) : value(v) {}
double operator[](size_t) const { return value; }
};
template<typename Expr>
auto operator*(const VecExpression<Expr>& expr, double scalar) {
return MultiplyVec<Expr, Scalar>(static_cast<const Expr&>(expr), Scalar(scalar));
}
// Similar for scalar on left-hand side and other operators...
Key Points:
Scalar
wrapper provides uniform operator[]
interface.4. Dynamic-Sized Expressions
Task: Implement a DynamicExpr
template supporting runtime-sized expressions (e.g., vectors of unknown size).
Requirements:
Test Case:
DynamicVector<double> X{1, 2}, Y{3, 4, 5};
try { auto Z = X + Y; } // Throws std::invalid_argument("Size mismatch")
catch(...) { ... }
Solution Code:
template<typename Expr>
class DynamicVecExpression {
public:
size_t size() const { return static_cast<const Expr&>(*this).size(); }
double operator[](size_t i) const { ... }
};
class DynamicVector : public DynamicVecExpression<DynamicVector> {
std::vector<double> data;
public:
size_t size() const { return data.size(); }
// ... Assignment operator with size check ...
};
template<typename L, typename R>
class AddDynamicVec : public DynamicVecExpression<AddDynamicVec<L, R>> {
const L& lhs; const R& rhs;
public:
AddDynamicVec(const L& l, const R& r) : lhs(l), rhs(r) {
if(lhs.size() != rhs.size()) throw std::invalid_argument("Size mismatch");
}
double operator[](size_t i) const { return lhs[i] + rhs[i]; }
};
Key Points:
size()
and operator[]
.5. Conditional Expressions
Task: Implement WhereExpr
to support conditional evaluation like C = Where(A > B, A, B)
.
Requirements:
Test Case:
Vector<int> A{5, 2, 7}, B{3, 4, 1};
Vector<int> C = Where(A > B, A, B); // C = {5, 4, 7}
Solution Code:
template<typename Cond, typename TrueExpr, typename FalseExpr>
class WhereExpr : public VecExpression<WhereExpr<Cond, TrueExpr, FalseExpr>> {
const Cond& cond;
const TrueExpr& true_expr;
const FalseExpr& false_expr;
public:
WhereExpr(const Cond& c, const TrueExpr& t, const FalseExpr& f)
: cond(c), true_expr(t), false_expr(f) {}
double operator[](size_t i) const {
return cond[i] ? true_expr[i] : false_expr[i];
}
};
template<typename C, typename T, typename F>
WhereExpr<C, T, F> Where(const VecExpression<C>& c,
const VecExpression<T>& t,
const VecExpression<F>& f) {
return {static_cast<const C&>(c), static_cast<const T&>(t), static_cast<const F&>(f)};
}
Key Points:
Multiple Choice Answers:
constexpr
(A) enhances compile-time evaluation; Concepts © constrain templates.Design Question Keys:
sin()
, exp()
) using proxy objects.All code examples compile with:
g++ -std=c++17 -O2 main.cpp