0

我在 Code Review 中发布了一个用 C++ 编写的简单 n-body 类。

在那里我被告知使用std::valarray而不是简单std::array的目标是我可以重写一些目前看起来像这样的代码

void Particle::update_position() {
    for (unsigned int d = 0; d < DIM; ++d) {
        position[d] += dt*(velocity[d] + a*force_new[d]);
        force_old[d] = force_new[d];
    }
}

对此

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

因为我是 C++ 的初学者,所以我写了一个使用 C++ 的简短程序,std::valarray以便我可以学习如何使用这种数据结构。但是,编译器会引发很多错误,我不知道为什么。我希望你能帮我解决这个问题。这是我写的小程序:

#include <iostream>
#include <vector>
#include <array>
#include <valarray>

constexpr unsigned int DIM = 2;

struct Particle{
    std::array<std::valarray<double>, DIM> position; 
    std::array<std::valarray<double>, DIM> velocity;
    std::array<std::valarray<double>, DIM> force_new;
    std::array<std::valarray<double>, DIM> force_old;
    void update_position();
};

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

void compute_position(std::vector<Particle>& particles) {
    for (auto& particle: particles) { 
        particle.update_position();
    }
}

void print_data(const std::vector<Particle>& particles) {
    for (const auto& particle: particles) {
        for (const auto& x: particle.position) std::cout << x << " ";
        for (const auto& v: particle.position) std::cout << v << " ";
        for (const auto& F: particle.position) std::cout << F << " ";
        std::cout << std::endl;
    }
}

void init_data(std::vector<Particle>& particles) {
    for (auto& particle: particles) {
        for (const auto& p: particle) {
            p.position = 1.0
            p.velocity = 2.0
            p.force_new = 3.0
            p.force_old = 4.0
        }
    }
}

int main() { 
    const unsigned int n = 10;
    std::vector<Particle> particles(n);
    init_data(particles);
    compute_position(particles);
    print_data(particles); 
    return 0;
}

当我尝试编译此代码时,出现以下错误:

so.cpp: In member function ‘void Particle::update_position()’:
so.cpp:17:31: error: no match for ‘operator+’ (operand types are ‘std::array<std::valarray<double>, 2>’ and ‘std::array<std::valarray<double>, 2>’)
     position += 0.1*(velocity + force_new);

so.cpp: In function ‘void print_data(const std::vector<Particle>&)’:
so.cpp:29:58: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘const std::valarray<double>’)
         for (const auto& x: particle.position) std::cout << x << " ";


so.cpp: In function ‘void init_data(std::vector<Particle>&)’:
so.cpp:38:29: error: ‘begin’ was not declared in this scope
         for (const auto& p: particle) {
                             ^~~~~~~~
so.cpp:38:29: note: suggested alternative:
In file included from so.cpp:4:
/usr/include/c++/8/valarray:1211:5: note:   ‘std::begin’
     begin(const valarray<_Tp>& __va)
     ^~~~~
so.cpp:38:29: error: ‘end’ was not declared in this scope
         for (const auto& p: particle) {
4

3 回答 3

1

你的成员Particlestd::arrayofstd::valarraydouble,不只是简单的std::valarray。意思是,标准库只提供operator+, operator*, [...],std::valarray其余的你需要自己提供。

例如,以下将解决该行的第一个编译器错误position += 0.1*(velocity + force_new); 参见在线直播

template<typename ValArray, std::size_t N>
auto operator+(std::array<ValArray, N> lhs, const std::array<ValArray, N>& rhs)
{
   for (std::size_t idx{}; idx < N; ++idx)
      lhs[idx] += rhs[idx];
   return lhs;
}


template<typename ValArray, std::size_t N, typename T>
auto operator*(T constant, std::array<ValArray, N> rhs)
{
   for (std::size_t idx{}; idx < N; ++idx)
      rhs[idx] *= constant;
   return rhs;
}

或者将其包装std::array<std::valarray<double>, DIM>到一个类中并提供必要的操作符来进行处理。见在线直播

#include <array>
#include <valarray>    

template<typename Type, std::size_t N>
class ValArray2D final
{
   std::valarray<std::valarray<Type>> _arra2D;
public:
   explicit constexpr ValArray2D(const std::valarray<Type>& valArray) noexcept
      : _arra2D{ valArray , N }
   {}

   ValArray2D& operator+=(ValArray2D rhs) noexcept
   {
      _arra2D += rhs._arra2D;
      return *this;
   }

   ValArray2D& operator*(Type constant) noexcept
   {
      for(auto& valArr: this->_arra2D)
         valArr = valArr * constant;
      return *this;
   }

   void operator=(Type constant) noexcept
   {
      for (auto& valArr : this->_arra2D)
         valArr = constant;
   }

   friend ValArray2D operator+(ValArray2D lhs, const ValArray2D& rhs) noexcept
   {
      lhs += rhs;
      return lhs;
   }

   friend std::ostream& operator<<(std::ostream& out, const ValArray2D& obj) noexcept
   {
      for (const std::valarray<Type>& valArray : obj._arra2D)
         for (const Type element : valArray)
            out << element << " ";
      return out;
   }
};

template<typename Type, std::size_t N>
ValArray2D<Type, N> operator*(Type constant, ValArray2D<Type, N> lhs)
{
   return lhs = lhs * constant;
}
于 2019-10-28T19:19:59.837 回答
1

首先,当您编写或更改代码时,请始终从第一个工作版本开始,并确保代码在每个步骤之间进行编译。这将使隔离错误编译的代码变得更加容易。

我为什么要告诉你这些?这是因为您的代码中的某些部分从未正确编译过。无论是在引入 valarray 之前还是之后。

例如,这个:

for (auto& particle : particles) {
    for (const auto& p: particle) {
        p.position = 1.0
        p.velocity = 2.0
        p.force_new = 3.0
        p.force_old = 4.0
    }
}

单个粒子不是可迭代类型,行尾没有分号。

只需一步一步地进行,并确保代码在每个步骤之间进行编译。


其次,我不认为 valarray 是你要找的。除非您希望每个粒子的每个属性都具有动态的维数,否则这将是非常令人惊讶的。

我建议您引入一种 vec 类型,该类型将具有您进行简化所需的组件。这种 vec 类型可以在库中找到,例如glm提供一个vec2具有诸如等运算符的类+-/*

即使没有库,也可以创建简单的矢量类型。

这是使用向量(在数学意义上)而不是使用向量的代码示例std::valarray

struct Particle{
    glm::vec2 position; 
    glm::vec2 velocity;
    glm::vec2 force_new;
    glm::vec2 force_old;
    void update_position();
};

void Particle::update_position() {
    position += 0.1*(velocity + force_new);
    force_old = force_new;
}

void compute_position(std::vector<Particle>& particles) {
    for (auto& particle: particles) { 
        particle.update_position();
    }
}

void print_data(const std::vector<Particle>& particles) {
    for (const auto& particle : particles) {
        std::cout << particle.position.x << ", " << particle.position.y << " ";
        std::cout << particle.velocity.x << ", " << particle.velocity.y << " ";
        std::cout << particle.force_new.x << ", " << particle.force_new.y << " ";
        std::cout << std::endl;
    }
}

void init_data(std::vector<Particle>& particles) {
    for (auto& particle : particles) {
        particle.position = {1, 2};
        particle.velocity = {2, 24};
        particle.force_old = {1, 5};
        particle.force_new = {-4, 2};
    }
}

int main() { 
    const unsigned int n = 10;
    std::vector<Particle> particles(n);
    init_data(particles);
    compute_position(particles);
    print_data(particles); 
    return 0;
}

具有自定义(不完整)vec2 类型的实时示例

于 2019-10-28T19:40:01.407 回答
1

好的,让我们解释一些概念,以便您了解错误的原因。

std::valarray vs 普通 c++ 数组 vs std::array

当我们谈论普通的 c++ 数组时,我们指的是如下内容

double myArray[DIM];

这只是 C++ 中数组的原生形式。然后我们有 std::array

std::array<double, DIM> myStdArry;

这只是一个围绕普通 c++ 数组的包装类,不同之处在于您可以访问一些实用程序函数以便更轻松地操作数据,即

myStdArry.size() //get the number of elements
myStdArry.begin() & myStdArry.end() //iterators to the begin and end of the array
myStdArry.fill(1.4) //replace all the values of the array to 1.4

对于普通数组和标准数组,您必须使用下标运算符 ([]) 才能访问数组的每个元素,即

for (size_t i = 0; i < DIM /*or myStdArry.size()*/; ++i;) {
  myArray[i] = ...;
} 

//Or with range-based for loop

for (auto& element : myArray) {
  element = ...;
}

因此,您不能直接在容器(数组)上使用算术运算符,因为它们没有为它们重载。这是 valarray 出现的时候,因为它是专门为这种操作设计的。Valarray 只是另一种类型的数组,它通过将算术运算符应用于数组的每个元素来重载算术运算符。

即假设我们想要对数组的每个元素求平方。使用 plain 和 std 数组,我们可以通过以下方式实现:

for (auto& element : myStdArray) {
  element *= element;
}

但是使用 valarray 我们可以简单地做到:

myValArray *= myValArray;

我们得到相同的结果。

另一个主要区别是,虽然普通数组和标准数组都是固定大小的(你必须在编译时设置它的大小),但 val 数组可以动态增长,这就是为什么你不必在声明时指定它的大小,但直到构造或以后

myValArray{DIM} //Or
myValArray.resize(DIM)
于 2019-10-28T19:40:40.163 回答