什么是 Expression Templates
Expression Templates 是一种 C++ 模板元编程技术,它通过在编译时构建按需执行的计算表达式,从而生成高效的代码。简单来说,通过 Expression Templates,我们可以实现惰性求值和消除因为中间结果而创建的临时变量。
一个常规示例
我们构造了一个MyVector
类,并且重载了MyVector
的+
和*
操作符,实现两个MyVector
中相同下标元素的+
和*
操作。
对于这样的需求我们很容易写出形如下面代码的一个简单的实现:
1#include <cassert>
2#include <iostream>
3#include <vector>
4
5template<typename T> class MyVector
6{
7public:
8 MyVector(const std::size_t n)
9 : vec_(n)
10 {}
11 MyVector(const std::size_t n, const T initvalues)
12 : vec_(n, initvalues)
13 {}
14
15 std::size_t size() const { return vec_.size(); }
16
17 T operator[](const std::size_t i) const
18 {
19 assert(i < size());
20 return vec_[i];
21 }
22
23 T& operator[](const std::size_t i)
24 {
25 assert(i < size());
26 return vec_[i];
27 }
28
29private:
30 std::vector<T> vec_;
31};
32
33template<typename T> MyVector<T> operator+(const MyVector<T>& a, const MyVector<T>& b)
34{
35 assert(a.size() == b.size());
36 MyVector<T> result(a.size());
37 for (std::size_t i = 0; i < a.size(); ++i) {
38 result[i] = a[i] + b[i];
39 }
40 return result;
41}
42
43template<typename T> MyVector<T> operator*(const MyVector<T>& a, const MyVector<T>& b)
44{
45 assert(a.size() == b.size());
46 MyVector<T> result(a.size());
47 for (std::size_t i = 0; i < a.size(); ++i) {
48 result[i] = a[i] * b[i];
49 }
50 return result;
51}
52
53template<typename T> std::ostream& operator<<(std::ostream& os, const MyVector<T>& vec)
54{
55 std::cout << '\n';
56 for (std::size_t i = 0; i < vec.size(); ++i) {
57 os << vec[i] << ' ';
58 }
59 os << '\n';
60 return os;
61}
62
63int main(int argc, char* argv[])
64{
65 MyVector<double> x(10, 5.4);
66 MyVector<double> y(10, 10.3);
67 auto ret = x + x + y * y;
68 std::cout << ret << std::endl;
69 return 0;
70}
这个实现平淡无奇,相信每个人都能随手写出来。在godbolt上编译成汇编来分析:
我们能发现,对于x + x + y * y
这行代码,执行的过程为:
temp1 = x + x
temp2 = y * y
temp3 = temp1 + temp2
优化后的版本
在上面的版本中,虽然实现起来很简单,但是会造成一些额外的临时变量。是的,这是我们不能容忍的。于是我们需要探索出一个如下图所示的更好的实现:
在这个优化的版本中,不需要为表达式result[i] = x[i] + x[i] + y[i] * y[i]
创建临时变量,赋值操作会直接触发运算的执行。
1#include <cassert>
2#include <iostream>
3#include <vector>
4
5template<typename T, typename Cont = std::vector<T>> class MyVector
6{
7public:
8 MyVector(const std::size_t n)
9 : vec_(n)
10 {}
11 MyVector(const std::size_t n, const T initvalues)
12 : vec_(n, initvalues)
13 {}
14
15 MyVector(const Cont& other)
16 : vec_(other)
17 {}
18
19 template<typename T2, typename R2> MyVector& operator=(const MyVector<T2, R2>& other)
20 {
21 assert(size() == other.size());
22 for (std::size_t i = 0; i < size(); ++i) vec_[i] = other[i];
23 return *this;
24 }
25
26 std::size_t size() const { return vec_.size(); }
27 T operator[](const std::size_t i) const { return vec_[i]; }
28 T& operator[](const std::size_t i) { return vec_[i]; }
29 const Cont& data() const { return vec_; }
30 Cont& data() { return vec_; }
31
32private:
33 Cont vec_;
34};
35
36template<typename T, typename Op1, typename Op2> class MyVectorAdd
37{
38public:
39 MyVectorAdd(const Op1& a, const Op2& b)
40 : op1_(a)
41 , op2_(b)
42 {}
43
44 T operator[](const std::size_t i) const { return op1_[i] + op2_[i]; }
45 std::size_t size() const { return op1_.size(); }
46
47private:
48 const Op1& op1_;
49 const Op2& op2_;
50};
51
52template<typename T, typename Op1, typename Op2> class MyVectorMul
53{
54public:
55 MyVectorMul(const Op1& a, const Op2& b)
56 : op1_(a)
57 , op2_(b)
58 {}
59
60 T operator[](const std::size_t i) const { return op1_[i] * op2_[i]; }
61 std::size_t size() const { return op1_.size(); }
62
63private:
64 const Op1& op1_;
65 const Op2& op2_;
66};
67
68template<typename T, typename R1, typename R2>
69MyVector<T, MyVectorAdd<T, R1, R2>> operator+(const MyVector<T, R1>& a, const MyVector<T, R2>& b)
70{
71 return MyVector<T, MyVectorAdd<T, R1, R2>>(MyVectorAdd<T, R1, R2>(a.data(), b.data()));
72}
73
74template<typename T, typename R1, typename R2>
75MyVector<T, MyVectorMul<T, R1, R2>> operator*(const MyVector<T, R1>& a, const MyVector<T, R2>& b)
76{
77 return MyVector<T, MyVectorMul<T, R1, R2>>(MyVectorMul<T, R1, R2>(a.data(), b.data()));
78}
79
80template<typename T> std::ostream& operator<<(std::ostream& os, const MyVector<T>& vec)
81{
82 os << '\n';
83 for (std::size_t i = 0; i < vec.size(); ++i) os << vec[i] << ' ';
84 os << '\n';
85 return os;
86}
87
88int main(int argc, char* argv[])
89{
90 MyVector<double> x(10, 5.4);
91 MyVector<double> y(10, 10.3);
92 MyVector<double> result(10);
93 result = x + x + y * y;
94 std::cout << result << std::endl;
95 return 0;
96}
对于这个实现,同样在godbold中进行分析:
汇编代码片段中表达式虽然很长,但是仔细看还是能看清它的结构。下面是一个简化版的代码生成图,用来说明模板的生成过程:
参考文档
https://www.modernescpp.com/index.php/avoiding-temporaries-with-expression-templates
评论