38

I am using visual studio 2010 and I am trying to use std::copy_if, I want to copy all values that are satisfying a predicate. For example:

struct comp
{
    bool operator()(const int i) { return i == 5 || i == 7; }
};

int main()
{
    array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
    vector<int> res;
    copy_if(arr.begin(), arr.end(), res.begin(), comp());

    for(int i = 0; i < res.size(); i++)
    {
        cout << res[i] << endl;
    }

    return 0;
}

But when I run this code I get: vector iterator not incrementable.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
Merni
  • 2,842
  • 5
  • 36
  • 42

4 Answers4

67

The copy_if algorithm looks something like this(taken from MSVC2010):

template<class InIt, class OutIt, class Pr> inline
OutIt copy_if(InIt First, InIt Last, OutIt Dest, Pr Pred)
{
    for (; First != _Last; ++First)
        if (Pred(*_First))
            *Dest++ = *First;
    return (Dest);
}

And as you can see the copy_if does not do a push_back, it just copy the value on the position where the iterator is, and then increments the iterator. What you want do use instead is the std::back_inserter, which pushes the element back of your vector. And if you are using MSVC2010 you can use Lambda instead of a function object, which Microsoft offers as an extension(C++0x)

int main()
{
    array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
    vector<int> res;
    copy_if(arr.begin(), arr.end(), back_inserter(res),[](const int i) { return i == 5 || i == 7; });

    for(unsigned i = 0; i < res.size(); i++)
        cout << res[i] << endl;

    return 0;
}
hidayat
  • 9,493
  • 13
  • 51
  • 66
  • 6
    While this answer comes to a correct conclusion, the explanation is rather confusing. The error has nothing to do with the vector being empty. The algorithm simply expects an iterator that satisfies the output-iterator concept. Also, in your final code example you use a lambda expression for the predicate, which is not supported in current standard C++ (and the OP does not mention C++0x). – Björn Pollex Mar 25 '11 at 13:28
  • 3
    But copy_if is not part of the current standard either, but both copy_if and lambda are part of the recent draft of C++0x – hidayat Mar 25 '11 at 13:35
  • @hidyat: You right, however, Microsoft offers it as a custom extension. If the OP does not tag their question C++0x, you should at least mention that it your answer. – Björn Pollex Mar 25 '11 at 13:38
  • I find it a bit strange to call C++0x or Lambdas a "custom" extension. There is nothing custom about them. From next year on (when the new standard - hopefully - gets released) any compiler which doesn't support it will be considered nonconforming. – Fabio Fracassi Mar 25 '11 at 13:52
  • 1
    @Fabio: *Custom* might be a bad word. @hidayat: Please forget my first comment, it is wrong (as @visitor explained to me). – Björn Pollex Mar 25 '11 at 14:30
23

Should perf be a concern, consider instead of using std::back_inserter to populate the destination vector (an approach that involves an arbitrary number of costly destination vector reallocations), call std::copy_if with a source-sized destination vector followed by dest.erase(iteratorReturnedByCopyIf, dest.end()) - an approach that involves one allocation up front then one reallocation for the erase().

Data

C++ std::copy_if performance by dest-writing algorithm

Code

#include <algorithm>
#include <chrono>
#include <functional>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>

long long MeasureMilliseconds(std::function<void()> func, unsigned iterations)
{
   auto beginTime = std::chrono::high_resolution_clock::now();
   for (unsigned i = 0; i < iterations; ++i)
   {
      func();
   }
   auto endTime = std::chrono::high_resolution_clock::now();
   long long milliseconds = std::chrono::duration_cast<
      std::chrono::milliseconds>(endTime - beginTime).count();
   return milliseconds;
}

bool IsEven(int i)
{
   return i % 2 == 0;
}

int main()
{
   const unsigned Iterations = 300000;
   for (size_t N = 0; N <= 100; N += 2)
   {
      std::vector<int> source(N);
      // Populate source with 1,2,...,N
      std::iota(std::begin(source), std::end(source), 1);

      long long backInserterMilliseconds = MeasureMilliseconds([&]
      {
         std::vector<int> dest;
         std::copy_if(std::begin(source), std::end(source), 
            std::back_inserter(dest), IsEven);
      }, Iterations);

      long long sourceSizeAndEraseMilliseconds = MeasureMilliseconds([&]
      {
         std::vector<int> dest(source.size());
         std::vector<int>::iterator copyIfIterator = std::copy_if(
            std::begin(source), std::end(source), std::begin(dest), IsEven);
         dest.erase(copyIfIterator, dest.end());
      }, Iterations);

      std::cout << "N=" << N << '\n';
      std::cout << "Default-size dest and back_inserter: " << 
         backInserterMilliseconds << '\n';
      std::cout << "      Source-sized dest and erase(): " << 
         sourceSizeAndEraseMilliseconds << "\n\n";
   }
   return 0;
}

Code Output

N=90
Default-size dest and back_inserter: 469
      Source-sized dest and erase(): 89

N=92
Default-size dest and back_inserter: 472
      Source-sized dest and erase(): 90

N=94
Default-size dest and back_inserter: 469
      Source-sized dest and erase(): 92

N=96
Default-size dest and back_inserter: 478
      Source-sized dest and erase(): 92

N=98
Default-size dest and back_inserter: 471
      Source-sized dest and erase(): 93

N=100
Default-size dest and back_inserter: 480
      Source-sized dest and erase(): 92

References

[alg.copy]
Qt ScatterChart

Neil Justice
  • 1,325
  • 2
  • 12
  • 25
  • 1
    How would the test for `std::back_inserter` differ if `dest.reserve(source.size())` were called before calling `copy_if`? – OnlineCop Aug 28 '18 at 19:28
  • On my machine, using Visual Studio 2017 - Visual C++ 14.1, dest.reserve(source.size()) is actually slightly faster... – Teris Oct 05 '19 at 22:38
16

You can use an output iterator:

copy_if(arr.begin(), arr.end(), std::back_inserter(res), comp());
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • 3
    While your answer comes to a correct conclusion, I find your explanation rather confusing. You do **not** have to insert new items in the vector, and you can use `copy_if` to overwrite existing items just fine, as long as the vector's size is sufficient. – visitor Mar 25 '11 at 14:24
7

Reserve the array size. hidayat gives the reason for this.

res.resize(arr.size());
Community
  • 1
  • 1
dubnde
  • 4,359
  • 9
  • 43
  • 63
  • 1
    @space: In a sense resizing helps alright. Vector's iterator works well as an output iterator (you can do `*it = n;` alright if `it` points to a valid location). For example you can do it like this, using the return value to erase the surplus items: http://ideone.com/QKnry. Or you may use count_if to determine how large the result vector needs to be. Whether these are good ways is another question. – visitor Mar 25 '11 at 14:17
  • As @visitor explained, your answer is indeed correct. However, I cannot remove the downvote unless you edit your answer. – Björn Pollex Mar 25 '11 at 14:28
  • @Space_C0wb0y I have done a minor edit to enable you to upvote – dubnde Mar 25 '11 at 16:58
  • 2
    You'll also need to trim the extra elements after copying the correct ones, perhaps by `res.erase(copy_if(...),res.end())`. In my opinion, using a `back_inserter` would be easier to follow. – Mike Seymour Mar 25 '11 at 17:38