什么是 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上编译成汇编来分析:

my_vector1

我们能发现,对于x + x + y * y这行代码,执行的过程为:

my_vector1.1

  1. temp1 = x + x
  2. temp2 = y * y
  3. temp3 = temp1 + temp2

优化后的版本

在上面的版本中,虽然实现起来很简单,但是会造成一些额外的临时变量。是的,这是我们不能容忍的。于是我们需要探索出一个如下图所示的更好的实现:

my_vector2.1

在这个优化的版本中,不需要为表达式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中进行分析:

my_vector2.2

汇编代码片段中表达式虽然很长,但是仔细看还是能看清它的结构。下面是一个简化版的代码生成图,用来说明模板的生成过程:

Exression

参考文档

https://www.modernescpp.com/index.php/avoiding-temporaries-with-expression-templates