简体   繁体   中英

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

I have a particle class which keeps the position, velocity and the acceleration of an object. I can access the related part of the vector via .position() , .velocity() and .acceleration() . I also can access each single number like .velocity_x() , .velocity_y() , .velocity_z() etc. what I would like to do is to access the z part of velocity this way:

p.velocity().z()

I would like to use both .velocity() (as a subvector) and .velocity().z() (as a single number) at the same time.

I also would like to avoid defining any extra variable. I prefer everything be interpreted at the compile time (due to performance priority).

Is this implementation possible in c++?

In an attempt, I have considered returning another class with a functor. But beside the problem of compile time interpretation there is an issue with const as .velocity() does not know whether it will be followed by a const or non- const form of .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

make:

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

Armadillo documentation

Well, one possible solution would be to return a proxy object that both exposes the .{x|y|z}() member functions and is implicitly convertible to your vector type.

Conceptually like this:

#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); } 
};

Why a template? Because I would imagine you'd want different behavior, depending on the value category of the particle object.

We wouldn't want to return a non-const reference to a particles internal data if that particle is an lvalue. If it's an rvalue, we may as well return an rvalue reference as well so the code behaves "as expected".

The member function velocity() of the particle can have a value category qualifier to differentiate the two cases. And the template above just captures common behavior and abstracts the differences.

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
};

Since you clarified in the comments that velocity always shuold returns a new vector object by value (an excellent default approach, btw), while allowing to modify the particle as well. The solution above needs to be updated:

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)
}

And that should be it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM