Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine std::copy_if and std::transform?

Tags:

c++

std

consider this code snippet : iteration over one container of a first type T1 for creating a second container of a second type T2 applying a transformation function T1->T2 but only for T1 elements verifying a predicate (T1 -> bool )

(is Odd in the following example).

std::vector<int> myIntVector;
myIntVector.push_back(10);
myIntVector.push_back(15);
myIntVector.push_back(30);
myIntVector.push_back(13);

std::vector<std::string> myStringVectorOfOdd;

std::for_each(myIntVector.begin(), myIntVector.end(),
    [&myStringVectorOfOdd](int val)
{
    if (val % 2 != 0)
        myStringVectorOfOdd.push_back(std::to_string(val));

});

What I don't like in this code is the capture on the lambda. Is there a way to combine std::copy_if and std::transform to achieve the same result in a more elegant and concise way ?

like image 920
sandwood Avatar asked Sep 19 '18 07:09

sandwood


People also ask

What is std:: transform?

std::transformApplies an operation sequentially to the elements of one (1) or two (2) ranges and stores the result in the range that begins at result . (1) unary operation. Applies op to each of the elements in the range [first1,last1) and stores the value returned by each operation in the range that begins at result ...

Is std :: transform faster?

I timed the difference between both implementations using google benchmark and came to the conclusion that the loop is about 5 times faster than using std::transform.

How do you use std transform?

std::transform on a range For example, to obtain the keys that a map contains, you can use std::transform the following way: map<int, string> m = { {1,"foo"}, {42, "bar"}, {7, "baz"} }; vector<int> keys; std::transform(m. begin(), m. end(), std::back_inserter(keys), getFirst);

When to use std:: copy?

std::copy. Copies the elements in the range [first,last) into the range beginning at result . The function returns an iterator to the end of the destination range (which points to the element following the last element copied).


1 Answers

Here is a transform_if template that takes the usual input iterator pair, an output iterator and a predicate as well as a transformation function object.

template <class InputIt, class OutputIt, class Pred, class Fct>
void transform_if(InputIt first, InputIt last, OutputIt dest, Pred pred, Fct transform)
{
   while (first != last) {
      if (pred(*first))
         *dest++ = transform(*first);

      ++first;
   }
}

You can use it for your example like the following.

transform_if(myIntVector.cbegin(), myIntVector.cend(),
    std::back_inserter(myStringVectorOfOdd),
    [](int n){ return n % 2 != 0; },
    [](int n){ return std::to_string(n); });

It's not super concise, but filtering and transformation are well separated into to capture-free lambdas, and the algorithm itself idiomatically works on iterators.

As range libraries offer better support for composing algorithms, here is the same based on Boost range:

#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

using boost::adaptors::filtered;

boost::transform(myIntVector | filtered([](int n){ return n % 2 != 0; }),
    std::back_inserter(myStringVectorOfOdd), [](int n){ return std::to_string(n); });
like image 168
lubgr Avatar answered Sep 20 '22 18:09

lubgr