我正在重写项目的矢量数学部分,我想根据它们的类型和维数来概括向量。 vector<T, N>
表示类型为T的N维向量。
template<typename T, int N>
struct vector {
T data[N];
};
我需要重写许多数学函数,其中大部分都是基于每个组件运行的。加法运算符的直接实现如下所示。
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
for (int i = 0; i < N; i++) {
result[i] = lhs[i] + rhs[i];
}
return result;
}
我的问题:有没有办法(通过模板技巧?)实现这一点而不使用for
循环和临时变量?据我所知,编译器很可能会展开循环并对其进行优化。我只是不喜欢以这种方式实现所有性能关键数学函数的想法。它们都将被内联并且在标题中,因此拥有许多这些函数也会产生一个丑陋的头文件。
我想知道是否有办法做到这一点会产生更优的源代码。可能像variadic模板一样工作的方式。有点像这样。
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
return vector<T, N>(lhs[0] + rhs[0], lhs[1] + rhs[1]...);
}
答案 0 :(得分:0)
这样做的一种方法是通过较低级别&#34; map&#34;功能:
这是一个完整的工作示例
#include <iostream>
#include <math.h>
template<typename T, int N>
struct vector {
T data[N];
};
首先声明你的工人&#34; map&#34;函数 - 我在这里{3} map
,map2
,foreach
。
template<typename T, int N, typename FN>
static void foreach(const vector<T,N> & vec, FN f) {
for(int i=0; i<N ;++i) {
f(vec.data[i]);
}
}
template<typename T, int N, typename FN>
static auto map(const vector<T,N> & vec, FN f) -> vector<decltype(f(T(0))), N> {
vector<decltype(f(T(0))), N> result;
for(int i=0; i<N ;++i) {
result.data[i] = f(vec.data[i]);
}
return result;
}
template<typename T1, typename T2, int N, typename FN>
static auto map2(const vector<T1,N> & vecA,
const vector<T2,N> & vecB,
FN f)
-> vector<decltype(f(T1(0), T2(0))), N> {
vector<decltype(f(T1(0), T2(0))), N> result;
for(int i=0; i<N ;++i) {
result.data[i] = f(vecA.data[i], vecB.data[i]);
}
return result;
}
现在使用帮助器通过lambdas定义更高级别的功能。我将定义二进制+,二进制 - ,一元 - 和e^x
。哦和operator<<
所以我们可以看到发生了什么。
我非常确定这是operator+
和operator-
中使用的lambdas的更好替代品,但我无法记住它们
template<typename T, int N>
vector<T,N> operator+(const vector<T,N> &lhs, const vector<T,N> &rhs) {
return map2(lhs, rhs, [](T a,T b) { return a+b;} );
}
template<typename T, int N>
vector<T,N> operator-(const vector<T,N> &lhs, const vector<T,N> &rhs) {
return map2(lhs, rhs, [](T a,T b) { return a-b;} );
}
template<typename T, int N>
vector<T,N> operator-(const vector<T,N> &vec) {
return map(vec, [](T a) { return -a;} );
}
template<typename T, int N>
auto exp(const vector<T,N> &vec) -> vector<decltype(exp(T(0))), N> {
return map(vec, [](T a) { return exp(a); } );
}
template<typename T, int N>
std::ostream & operator<<(std::ostream& os, const vector<T,N> &vec) {
os<<"{";
foreach(vec, [&os](T v) { os<<v<<", "; } );
os<<"}";
return os;
}
现在看看它们如何正常工作......
int main() {
vector<int, 5> v1 = {1,2,3,4,5};
vector<int, 5> v2 = {2,4,6,8,10};
std::cout<<v1 << " + " << v2 << " = " << v1+v2<<std::endl;
std::cout<<v1 << " - " << v2 << " = " << v1-v2<<std::endl;
std::cout<<" exp( - " << v2 << " )= " << exp(-v1)<<std::endl;
}
答案 1 :(得分:0)
你可以这样做,我会指出你的解决方案(编译和运行)。你正在寻找摆脱循环,最好是通过内联它,希望编译器能为你优化。
在实践中,我发现足以指定所需的尺寸,即N = 3,4,5,因为这样可以更好地控制编译器所做的事情,而不是按照你的要求进行操作。但是,您可以使用递归和部分模板特化来实现您的运算符。我已经说明了补充。
所以不要这样:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
for (int i = 0; i < N; i++) {
result[i] = lhs[i] + rhs[i];
}
return result;
}
您希望代码能够有效地执行此操作:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
result[0] = lhs[0] + rhs[0];
result[1] = lhs[1] + rhs[1];
...
result[N-1] = lhs[N-1] + rhs[N-1];
return result;
}
如果N是1,你很容易想要这个...... 模板 vector operator +(vector lhs,vector rhs){ 矢量结果; result [0] = lhs [0] + rhs [0]; 返回结果; }
如果N是2,那么你很容易想要这个...... 模板 vector operator +(vector lhs,vector rhs){ 矢量结果; result [0] = lhs [0] + rhs [0]; 结果[1] = lhs [1] + rhs [1]; 返回结果; }
最简单的方法是简单地将其定义为您希望使用的N个,而不是您正在寻找的答案,因为在实践中您可能不需要超过N = 5或N = 6吗? / p>
但是,您也可以使用部分模板特化和递归来实现目标。考虑这个结构,它以递归方式调用自身,然后分配索引:
template<typename T, int N, int IDX>
struct Plus
{
void operator()(vector<T,N>& lhs, vector<T,N>& rhs, vector<T,N>& result)
{
Plus<T,N,IDX-1>()(lhs,rhs,result);
result.data[IDX] = lhs.data[IDX] + rhs.data[IDX];
}
};
这个部分特化似乎什么都不做,但是当索引为0并结束递归时处理这种情况:
template<typename T, int N>
struct Plus<T,N,-1>
{
void operator()(vector<T,N>& lhs, vector<T,N>& rhs, vector<T,N>& result)
{
//noop
}
};
最后是operator +的实现,它实例化了Plus并调用它:
template<typename T, int N>
vector<T, N> operator+(vector<T, N> lhs, vector<T, N> rhs) {
vector<T, N> result;
Plus<T,N,N-1>()(lhs,rhs,result);
return result;
}
你需要把它变成一个操作符,使它更通用但你明白了。然而,这对编译器来说意味着它在大型项目中可能需要一段时间,即使它非常酷。在实践中,我发现手工输入您想要的重载或编写脚本代码来生成C ++会产生更可调试的体验和代码,最终更易于阅读并且编译器更容易优化。更具体地说,如果你编写一个脚本来生成C ++,你可以首先包含SIMD内在函数,而不是让事情发生。
答案 2 :(得分:0)
要回答您的问题,您可以使用std::index_sequence
来构建,例如:
namespace detail
{
template<typename T, int N, std::size_t...Is>
vector<T, N> add(std::index_sequence<Is...>,
const vector<T, N>& lhs,
const vector<T, N>& rhs)
{
return {{ (lhs[Is] + rhs[Is])... }};
}
}
template<typename T, int N>
vector<T, N> operator+(const vector<T, N>& lhs, const vector<T, N>& rhs) {
return detail::add(std::make_index_sequence<N>{}, lhs, rhs);
}