每个组件操作的C ++矢量模板

时间:2016-03-16 04:22:03

标签: c++ templates vector metaprogramming variadic

我正在重写项目的矢量数学部分,我想根据它们的类型和维数来概括向量。 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]...);
}

3 个答案:

答案 0 :(得分:0)

这样做的一种方法是通过较低级别&#34; map&#34;功能:

这是一个完整的工作示例

#include <iostream>
#include <math.h>

template<typename T, int N>
struct vector {
    T data[N];
};

首先声明你的工人&#34; map&#34;函数 - 我在这里{3} mapmap2foreach

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)

  • 首先,编译器可能会展开循环。
  • 其次,为了获得更好的性能,请通过const引用而不是值来传递参数,以避免额外的副本。

要回答您的问题,您可以使用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);
}

Demo