Implementing access to vector of stucts using a ve

2019-08-11 07:38发布

问题:

Some higher-level languages have this feature, and I'm attempting to implement in C++. I'm not sure if there's a library that already does this somewhere, but if there is, it would save me a lot of trouble. Anyway, here's what I'm trying to accomplish:

Let's say I have a vector of structs that have a double and an int as members, and that I have another vector of ints that represents the indices of the elements in the vector of structs that I want to keep.

typedef struct
{
double x;
int y;
}s;

std::vector<s> v;
std::vector<int> inds;

Is there a way to implement accessing the elements in the structure using the vector of indices in a manner similar to this, or has it been implemented elsewhere?

std::vector<double> dResult = v[inds].x;
std::vector<int> iResult = v[inds].y;

It would also be nice to be able to access all of the elements in this manner:

std::vector<double> d = v.x;

Are these things possible?

回答1:

  1. You cannot use that syntax with existing definitions of std::vector.
  2. You cannot create a global operator overload function that provides that syntax since operator[]() can be overloaded only as a member function.
  3. You can create a non-member function that provides the functionality but without using that syntax.

template <typename T1, typename T2>
std::vector<T2> getElements(std::vector<T1> const& vec,
                            std::vector<int> const& indices,
                            T2 (T1::*member))
{
   std::vector<T2> ret;
   for ( auto index : indices )
   {
      ret.push_back(vec[index].*member);
   }
   return ret;
}

and then use it as:

std::vector<s> v;
std::vector<int> inds;
std::vector<double> dResult = getElements(v, inds, &s::x);


回答2:

No such built-in functionality exists, and I'm not aware of any existing library solutions either. But it's not too difficult to write a couple of function templates that do this for you.

template<typename Container, typename T, typename M>
std::vector<M> select_mem(Container const& c, M T::* mem)
{
    std::vector<M> result;
    result.reserve(c.size());

    std::transform(c.begin(), c.end(), std::back_inserter(result),
                   std::mem_fn(mem));
    return result;
}

The above template takes a reference to a container and a pointer to a data member. It then copies that data member from each element in the input container into the output vector.

template<typename Container, typename Indices, typename T, typename M>
std::vector<M> select_mem(Container const& c, Indices const& ind, M T::* mem)
{
    std::vector<M> result;
    result.reserve(ind.size());

    std::transform(ind.begin(), ind.end(), std::back_inserter(result),
                   [&c, mem](typename Indices::value_type const& i) {
                       return std::mem_fn(mem)(c[i]);
                   });
    return result;
}

This is an extension of the previous template that also accepts a container of indices, and only copies the data members at the indicated indices within the input container.

Use them as follows:

std::vector<s> v {{10, 1}, {20, 2}, {30, 3}, {40, 4}};

for(auto const& x : select_mem(v, &s::x)) {
    std::cout << x << ' ';
}
std::cout << '\n';

std::vector<int> indices{1,2};
for(auto const& x : select_mem(v, indices, &s::x)) {
    std::cout << x << ' ';
}
std::cout << '\n';

Live demo



回答3:

To get syntax close to what you desired you could create a class that wraps a vector of your structs and has member functions for each of the member variables in your struct:

class VecS {
  const std::vector<s>&   v;
  const std::vector<int>* inds;

  template<typename R>
  std::vector<R> impl(R s::* pm) const {
    if (inds) {
      std::vector<R> ret(inds->size());
      auto get_at_index = [this, pm](int index){ return v[index].*pm; };
      std::transform(inds->begin(), inds->end(), ret.begin(), get_at_index);
      return ret;
    }
    std::vector<R> ret(v.size());
    std::transform(v.begin(), v.end(), ret.begin(), std::mem_fn(pm));
    return ret;    
  }

public:
  VecS(const std::vector<s>& v) : v(v), inds(nullptr) {}
  VecS(const std::vector<s>& v, const std::vector<int>& inds) : v(v), inds(&inds) {}

  std::vector<double> x() const { return impl(&s::x); }
  std::vector<int>    y() const { return impl(&s::y); }
};

If you are willing to abuse operator[] you can go one step further and add something like:

VecS operator[](const std::vector<int>& inds) { return VecS(v, inds); }

And then you can write:

auto vs = VecS(v);
auto dResult = vs[inds].x(); 
auto iResult = vs[inds].y();

and of course:

auto d = vs.x();

Live demo.