子向量和成员的 C++ 编译时解释

C++ compile time interpretation of sub-vectors and members

我有一个粒子class,它保持物体的位置、速度和加速度。我可以通过 .position().velocity().acceleration() 访问向量的相关部分。我还可以访问每个数字,如 .velocity_x().velocity_y().velocity_z() 等。我想做的是通过这种方式访问​​速度的 z 部分:

p.velocity().z()

我想同时使用 .velocity()(作为子向量)和 .velocity().z()(作为单个数字)。

我也想避免定义任何额外的变量。我更喜欢在编译时解释所有内容(由于性能优先)。

这个实现可以用 c++ 实现吗?

在一次尝试中,我考虑过返回另一个带有仿函数的 class。但是除了编译时解释的问题之外,const 还存在一个问题,因为 .velocity() 不知道它后面是 const 还是非 const 形式的 .z().

#include <iostream>
#include <armadillo>

class Particle
{
public:
    arma::vec::fixed<9> data;

    inline double velocity_z() const
    {
        return data(5);
    }
    inline double& velocity_z()
    {
        return data(5);
    }

    inline const arma::subview_col<double> position() const
    {
        return data.subvec(0,2);
    }
    inline arma::subview_col<double> position()
    {
        return data.subvec(0,2);
    }
    inline const arma::subview_col<double> velocity() const
    {
        return data.subvec(3,5);
    }
    inline arma::subview_col<double> velocity()
    {
        return data.subvec(3,5);
    }
    inline const arma::subview_col<double> acceleration() const
    {
        return data.subvec(6,8);
    }
    inline arma::subview_col<double> acceleration()
    {
        return data.subvec(6,8);
    }
};

arma::vec vector3(double x,double y,double z)
{
    return {x,y,z};
}

int main()
{
    Particle p;
    p.position()=vector3(1.1,2.1,3.1);
    p.velocity()=vector3(1.2,2.2,3.2);
    p.velocity_z()=10.0;
    p.acceleration()=vector3(1.3,2.3,3.3);
    p.data.print();
    return 0;
}


// output:

//     1.1000
//     2.1000
//     3.1000
//     1.2000
//     2.2000
//    10.0000
//     1.3000
//     2.3000
//     3.3000

制作:

g++ -std=c++11 test1.cpp -larmadillo

Armadillo documentation

嗯,一个可能的解决方案是 return 一个代理对象,它既公开 .{x|y|z}() 成员函数又可以隐式转换为您的向量类型。

概念上是这样的:

#include <type_traits>

using vector_t = /* your library type */;

template<bool RValue>
class vector_proxy {
  using return_type = typename std::conditional<RValue, vector_t&&, vector_t const&>::type;
  vector_t &ref;
public:
  vector_proxy(vector_t &ref) : ref(ref) {}
  vector_proxy(vector_proxy const&) = delete;
  vector_proxy& operator=(vector_proxy const&) = delete;
  vector_proxy(vector_proxy&&) = delete;
  vector_proxy& operator=(vector_proxy&&) = delete;

  auto x() { /* code to produce x */ }
  auto y() { /* code to produce y */ }
  auto z() { /* code to produce z */ }

  operator return_type() { return static_cast<return_type>(ref); } 
};

为什么要模板?因为我想你会想要不同的行为,这取决于粒子对象的值类别。

如果粒子是左值,我们不希望 return 对粒子内部数据的非常量引用。如果它是一个右值,我们也可以 return 一个右值引用,所以代码的行为是 "as expected".

粒子的成员函数velocity()可以有一个值类别限定符来区分这两种情况。上面的模板只是捕获了共同的行为并抽象了差异。

class particle {
  // Members
public:
  vector_proxy<false> velocity() const& { return {/* A ref to the velocity member */}; }
  vector_proxy<true> velocity() && { return {/* A ref to the velocity member */}; }

  // More functionality
};

自从您在评论中澄清 velocity 始终按值 return 设置一个新的矢量对象(顺便说一句,这是一种很好的默认方法),同时也允许修改粒子。上面的解决方案需要更新:

class particle;

template<bool ByRef>
class vector_proxy {
  using return_type =
    typename std::conditional<ByRef, double&, double>::type;

  using ref_type =
    typename std::conditional<ByRef, particle&, particle const&>::type;

  ref_type ref;
public:
  vector_proxy(ref_type ref) : ref(ref) {}
  vector_proxy(vector_proxy const&) = delete;
  vector_proxy& operator=(vector_proxy const&) = delete;
  vector_proxy(vector_proxy&&) = delete;
  vector_proxy& operator=(vector_proxy&&) = delete;

  return_type x();
  return_type y();
  return_type z();

  operator vector_t(); 
};

class particle {
  // Members

  template<bool>
  friend class vector_proxy;
public:
  vector_proxy<false> velocity() const { return {*this}; }
  vector_proxy<true>  velocity()       { return {*this}; }

  // More functionality
};

template<bool ByRef>
auto vector_proxy<ByRef>::x -> return_type {
  return ref.data(3);
}

template<bool ByRef>
auto vector_proxy<ByRef>::y -> return_type {
  return ref.data(4);
}

template<bool ByRef>
auto vector_proxy<ByRef>::z -> return_type {
  return ref.data(5);
}

template<bool ByRef>
vector_proxy<ByRef>::operator vector_t() {
  return ref.data.subvec(3, 5)
}

应该就是这样了。