C++ 表达式模板 - 为什么基数 class?
C++ Expression Templates - Why the base class?
我最近偶然发现了 C++ 中的表达式模板。有一件事我不太了解它们的实现,这就是为什么需要基 class 的原因(与模板表达式相关的所有其他对象都以 CRTP 方式派生)。一个对向量进行加法和标量乘法的简单示例(类型 Vec
的对象,没有基数 class):
#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
对象,在任何情况下都会在过程中被销毁,为什么需要基础 class? (如 this 维基百科文章所示)
编辑:
我知道这段代码有点混乱和糟糕(在不必要的地方复制等),但我不打算使用这段特定的代码。这只是为了说明表达式模板在没有 CRTP 基础 class 的情况下在这个例子中工作 - 我试图弄清楚为什么这个基础 class 是必要的。
你的
template<typename E1, typename E2>
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2)
将匹配 任何 用户定义的类型,而不仅仅是表达式类型。当用非向量类型实例化时,它可能会失败。这与其他 C++ 类型的交互非常糟糕,很可能包括标准库类型,它们提供自己的自定义 operator +
并且可能依赖于在隐式转换后解析为自己的 operator +
的不精确匹配。
使 operator +
仅适用于 VecExpression<E>
可避免该问题。
我最近偶然发现了 C++ 中的表达式模板。有一件事我不太了解它们的实现,这就是为什么需要基 class 的原因(与模板表达式相关的所有其他对象都以 CRTP 方式派生)。一个对向量进行加法和标量乘法的简单示例(类型 Vec
的对象,没有基数 class):
#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
对象,在任何情况下都会在过程中被销毁,为什么需要基础 class? (如 this 维基百科文章所示)
编辑:
我知道这段代码有点混乱和糟糕(在不必要的地方复制等),但我不打算使用这段特定的代码。这只是为了说明表达式模板在没有 CRTP 基础 class 的情况下在这个例子中工作 - 我试图弄清楚为什么这个基础 class 是必要的。
你的
template<typename E1, typename E2>
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2)
将匹配 任何 用户定义的类型,而不仅仅是表达式类型。当用非向量类型实例化时,它可能会失败。这与其他 C++ 类型的交互非常糟糕,很可能包括标准库类型,它们提供自己的自定义 operator +
并且可能依赖于在隐式转换后解析为自己的 operator +
的不精确匹配。
使 operator +
仅适用于 VecExpression<E>
可避免该问题。