1

我最近偶然发现了 C++ 中的表达式模板。关于它们的实现,我不太了解一件事,这就是为什么需要基类(与模板表达式相关的所有其他对象都以 CRTP 方式派生)。一个简单的例子,对向量(类型的对象Vec,没有基类)进行加法和标量乘法:

#include <vector>
#include <iostream>
using namespace std;

class Vec
{
    vector<double> data;
public:

    template<typename E>
    Vec(E expr)
    {
        data = vector<double>(expr.size());
        for (int i = 0; i < expr.size(); i++)
            data[i] = expr[i];
    }
    Vec(int size)
    {
        data = vector<double>(size);
        for (int i = 0; i < size; i++)
            data[i] = i;
    }
    double operator [] (int idx) {
        return data[idx];
    }

    int size() { return data.size(); }

    bool operator < (Vec &rhs)
    {
        return (*this)[0] < rhs[0];
    }

    bool operator > (Vec &rhs)
    {
        return (*this)[0] > rhs[0];
    }

};

template<typename E1, typename E2>
class VecAdd
{
    E1 vec_expr1;
    E2 vec_expr2;

public:
    VecAdd(E1 vec_expr1, E2 vec_expr2) : vec_expr1(vec_expr1), vec_expr2(vec_expr2)
    {}

    double operator [] (int idx) { return vec_expr1[idx] + vec_expr2[idx]; }
    int size() { return vec_expr1.size(); }
};

template<typename E>
class ScalarMult
{
    E vec_expr;
    double scalar;

public:
    ScalarMult(double scalar, E vec_expr) : scalar(scalar), vec_expr(vec_expr)
    {}

    double operator [] (int idx) { return scalar*vec_expr[idx]; }
    int size() { return vec_expr.size(); }
};

template<typename E1, typename E2>
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2)
{
    return VecAdd<E1, E2>(vec_expr1, vec_expr2);
}

template<typename E>
ScalarMult<E> operator * (double scalar, E vec_expr)
{
    return ScalarMult<E>(scalar, vec_expr);
}

int main()
{
    Vec term1(5);
    Vec term2(5);

    Vec result = 6*(term1 + term2);
    Vec result2 = 4 * (term1 + term2 + term1);

    //vector<Vec> vec_vector = {result, result2};     does not compile
    vector<Vec> vec_vector;

    vec_vector = { result2, result };   //compiles

    vec_vector.clear();
    vec_vector.push_back(result);
    vec_vector.push_back(result2);      //all this compiles

    for (int i = 0; i < result.size(); i++)
        cout << result[i] << " ";
    cout << endl;

    system("pause");

    return 0;
}

上面的代码可以编译(除了指定的行),它可以正确地评估 main 函数中的简单表达式。如果表达式被分配给一个类型的对象Vec并将它们的内容分配给一个Vec对象,无论如何都会在这个过程中被破坏,为什么需要基类?(如维基百科文章所示)

编辑:

我知道这段代码有点混乱和糟糕(在不必要的地方复制等),但我不打算使用这个特定的代码。这只是为了说明表达式模板在没有 CRTP 基类的情况下在此示例中工作 - 我试图弄清楚为什么这个基类是必要的。

4

1 回答 1

3

您的

template<typename E1, typename E2>
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2)

将匹配任何用户定义的类型,而不仅仅是表达式类型。当用非向量类型实例化时,它可能会失败。这与其他 C++ 类型的交互非常糟糕,很可能包括标准库类型,这些类型提供自己的自定义并且可能依赖于在隐式转换后operator +解析为自己的不精确匹配。operator +

operator +可用于VecExpression<E>避免该问题。

于 2015-09-07T20:12:13.773 回答