سؤال

I have a large std::vector<int> a, but I would like to work only on a subset of it. The idea was to create a std::vector<reference_wrapper<int> > refa that only contains the said subset (in the mwe, all elements 1< a < 4). I would then like to pass refa to functions that expect a std::vector<int> or std::vector<int>& as arguments (because I also want to use them with a). a can be pretty large and I want to avoid to do the selection multiple times.

Questions

How do I properly pass refa to the functions? What I want is bar(refa) and foobar(refa) to work.

Is there a better way to solve the problem, without changing the functions (too much)?

Code

#include <functional>
#include <iostream>
#include <vector>


int foo(int &a)
{
  a++;
  return 0;
}

int bar(std::vector<int> &va)
{
  for(auto &vaa : va)
    vaa++;

  return 0;
}

int foobar(std::vector<int> va)
{
  for(auto &vaa : va)
    vaa++;

  return 0;
}



int main()
{
  std::vector<int> a= {1, 2, 3, 4, 5};
  std::vector<std::reference_wrapper<int> > refa;

  //Fill refa
  for(auto &aa : a)
    {
      if(aa>1 && aa<4) refa.push_back(std::ref(aa));
    }


  //works
  // for(auto &aa : refa)
  //   aa++;

  //works
  //  bar(a);

  //works, a unchanged
  //  foobar(a);

  //works
  // for(auto &aa : refa)
  //   foo(aa);

  //works                         
  // for(int &aa : refa)
  //  foo(aa)

  // does not work
  // conversion from vector<reference_wrapper<int> > to vector<int>& or vector<int> required
  bar(refa);
  //  foobar(refa);


  for(auto &aa : a)
    std::cout << aa << std::endl;


  return 0;
}

Note int is only used here to keep the example simple.

هل كانت مفيدة؟

المحلول

I would definitely use iterators especially considering your problem ahead (work on a subset of a vector):

template<class Iterator>
int bar(Iterator begin, Iterator end)
{
    for (auto it = begin; it != end; ++it)
        (*it)++;
    return 0;
}

So that not only you abstract away from the container, but you can also easily pass different iterators from the classical "begin" and "end" iterator to simulate specific ranges:

bar(a.begin() + 2, a.begin() + 4);

For example, with the above code you will visit elements from 1 to 4 (both excluded). And here's the live example.

نصائح أخرى

Your best bet is to make the functions bar and foobar template functions, like this:

template <typename TContainer> 
int bar(TContainer& va)
{
    for(auto& vaa : va)
        vaa++;

    return 0;
}

Without redefining your function to accept "types that look like vector", I don't think that there's a way of achieving what you want.

If you don't need a container, don't use a container. Use an iterable range.

A container is both an iterable range, and an owner of its contents.

In the case of a vector, it is a contiguous iterable range and an owner of the contents. Odds are you only need to know if is a random access iterable range in your implementation code.

However, dealing with arbitrary contiguous iterable ranges has a cost, in that you have to put your implementation in a template header file, or do expensive type erasure. Two ways to approach this is to use the fact that you are only accepting sub-ranges of a vector, or use the fact that you are only accepting sub-ranges of a contiguous range.

I like the contiguous range idea myself:

template<typename T>
struct contiguous_range {
  T* b; T* e;
  contiguous_range( contiguous_range const& ) = default;
  contiguous_range():b(nullptr), e(nullptr) {};
  contiguous_range& operator=( contiguous_range const& ) = default;
  std::size_t size() const { return e-b; }
  T* begin() const { return b; } // note, T*
  T* end() const { return e; } // note, T*
  template<typename U, typename=typename std::enable_if<
    sizeof(U)==sizeof(T)
    && std::is_convertible<U*, T*>::value
  >::type>
  contiguous_range( contiguous_range<U> const& o ):b(o.b), e(o.e) {};
  T& operator[]( std::size_t i ) const { return b[i]; }

  template<typename A>
  contiguous_range( std::vector<T, A> const& v ):b(v.data()), e(v.data()+v.size()) {}
  template<typename U, std::size_t N, typename=typename std::enable_if<
    sizeof(U)==sizeof(T)
    && std::is_convertible<U*, T*>::value
  >::type>
  contiguous_range( std::array<U, N> const& a ):b(a.data()), e(a.data()+a.size()) {}
  template<typename U, std::size_t N, typename=typename std::enable_if<
    sizeof(U)==sizeof(T)
    && std::is_convertible<U*, T*>::value
  >::type>
  contiguous_range( U(&a)[N] ):b(&a[0]), e((&a[0])+N) {}

  template<typename U, typename=typename std::enable_if<
    sizeof(U)==sizeof(T)
    && std::is_convertible<U*, T*>::value
  >::type>
  contiguous_range( U* b_, U* e_ ):b(b_), e(e_) {}
};

template<typename I>
auto contiguous_subrange( I b, I e )
-> contiguous_range<std::iterator_traits<I>::value_type>
{
  return {&*b, &*e};
}
template<typename C>
auto contiguous_subrange( C&& c, std::size_t start, std::size_t end )
-> decltype( contiguous_subrange( &c[start], &c[end] ) )
  { return ( contiguous_subrange( &c[start], &c[end] ) ) };

Now, our functions can simply take a contiguous_range<int> or continguos_range<const int>, and they can be implicitly passed a std::vector<int>.

You can also set up subranges of your std::vector that are equally contiguous.

Note that a constiguous_range<int> corresponds to a std::vector<int>&, and a contiguous_range<const int> corresponds to a std::vector<int> const&.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top