23

Sorry for a little bit beginner question. There are vector and vector of pairs

typedef std::vector <int> TItems;
typedef std::vector < std::pair <int, int> > TPairs;

Is there any way to transform all first items in pair to another vector in one step

int main ()
{
TItems items;
TPairs pairs;

pairs.push_back (std::make_pair(1,3));
pairs.push_back (std::make_pair(5,7));

std::transform( items.begin(), items.end(), items.begin(), comp ( &pairs ) );

return 0;
}

How to design a functor?

class comp
{
private:
     TPairs *pairs;

public:
    comp ( TPairs  *pairs_ ) : pairs ( pairs_) { }

    unsigned int operator () ( const unsigned int index ) const
    {
        return  (*pairs)[index].second != pairs->end();  //Bad idea
    }
};

Maybe there is some more user friendly method without lambda expressions and loops. Thanks for your help.

Soo Wei Tan
  • 3,262
  • 2
  • 34
  • 36
justik
  • 4,145
  • 6
  • 32
  • 53

6 Answers6

23

First of all, you should use a back_inserter as the third argument to transform so that the transformed values are pushed to the back of the vector.

Second, you need some sort of functor which takes a pair of ints and returns the first one. This should do:

int firstElement( const std::pair<int, int> &p ) {
    return p.first;
}

Now, to put the pieces together:

TPairs pairs;
pairs.push_back( std::make_pair( 1, 3 ) );
pairs.push_back( std::make_pair( 5, 7 ) );

TItems items;
std::transform( pairs.begin(), pairs.end(), std::back_inserter( items ),
                firstElement );

After this code, items contains 1 and 5.

Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • 3
    Is there any clever way to use [std::get<0>](http://en.cppreference.com/w/cpp/utility/tuple/get) instead of your custom function? – NHDaly Aug 13 '15 at 23:56
18

see frerich's or kotlinski's answer for C++03.

C++11 solution with lambda:

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               [](const std::pair<int, int>& p) { return p.first; });
stefaanv
  • 14,072
  • 2
  • 31
  • 53
  • Oops, I didn't notice the "no lambda's" requirement, but why when it is straightforward and part of the language? – stefaanv Feb 01 '12 at 12:15
  • I believe it's not part of the C++ language which most of the people around here can actually use (either due to compiler limitations or because of some requirements by the work place). – Frerich Raabe Feb 01 '12 at 12:52
12

I really want you to use std::get as the functor, because it's already provided as a library function!!

Wouldn't it be great if we could write this line!?

std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

... But it's a bit more terrible than that. You need to disambiguate which get to use:

int main() {
  std::vector<int> items;
  std::vector<std::pair<int, int>> pairs;

  pairs.push_back(std::make_pair(1, 3));
  pairs.push_back(std::make_pair(5, 7));

  std::transform(pairs.begin(), pairs.end(), std::back_inserter(items),
                 (const int& (*)(const std::pair<int, int>&))std::get<0>);

  return 0;
}

The problem is, std::get is overloaded to take 1. pair&, 2. const pair&, and 3. pair&& as the parameters, so that it will work for any sort of pair as input. Unfortunately, the overloads get in the way of the template type deduction for std::transform, so our original line

std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

yields

 error: no matching function for call to ‘transform(std::vector<std::pair<int, int> >::iterator, std::vector<std::pair<int, int> >::iterator, std::back_insert_iterator<std::vector<int> >, <unresolved overloaded function type>)’
   std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);
                                                                                    ^
...

/usr/include/c++/4.8/bits/stl_algo.h:4915:5: note:   template argument deduction/substitution failed:
 note:   couldn't deduce template parameter ‘_UnaryOperation’
   std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

It doesn't know which overload of std::get you are asking for when deducing the template for std::transform, so you have to specify it manually. Casting the function pointer to the right type tells the compiler, "Hey, please use the overload where get takes a const& and returns a const&!"

But at least we're using standard library components (yay)?

And in terms of number of lines, it's no worse than the other options: http://ideone.com/6dfzxz

NHDaly
  • 7,390
  • 4
  • 40
  • 45
  • 1
    Can anyone think of any improvements? It'd be great to be able to cleanly use `std::get` like this. ...Really I should probably be using `reinterperet_cast&)>(std::get<0>)`, but that seems even worse... – NHDaly Aug 14 '15 at 01:19
  • I think it is possible to replace the "hard" cast with the get function wrapped inside a lambda for which one can specify the arguments – mr_T Oct 29 '15 at 10:47
3

another possibility from C++11 would be std::mem_fn, which is similar to solution with std::bind:

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               std::mem_fn(&std::pair<int,int>::first)               
);
Adrian W
  • 4,563
  • 11
  • 38
  • 52
Z.C. Tang
  • 31
  • 1
3

How about this?

items.reserve(pairs.size());
for (size_t it = 0; it < pairs.size(); ++it) {
    items.push_back(pairs[it].first);
}

Simple to understand and debug.

Johan Kotlinski
  • 25,185
  • 9
  • 78
  • 101
  • @ kotlinski: Thanks, but this is a common solution. I would like to find one-step solution without any loop, if possible. – justik Feb 01 '12 at 10:26
  • 1
    You asked for something user friendly, then it would be misleading to post an answer with some C++ atrocity :) – Johan Kotlinski Feb 01 '12 at 10:28
  • +1: simplest in this case. Why avoid loops if they simplify? – stefaanv Feb 01 '12 at 12:14
  • @stefaanv: Because the OP explicitely asked for an answer `without lambda expressions and loops.`. – Frerich Raabe Feb 01 '12 at 12:53
  • @Frerich: my why was actually referring to that limitation, but I admit that that wasn't clear. – stefaanv Feb 01 '12 at 12:55
  • @Frerich, don't get me wrong, I like your answer for staying closest to the original querstion, so I referenced it in my answer, but this one takes a little step back and says "hold on, it can be simpler" (I would still use iterators) – stefaanv Feb 01 '12 at 13:01
  • OP did not actually ask for loop-free solution. – Johan Kotlinski Feb 01 '12 at 13:57
3

How about using std::bind?

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               std::bind(&TPairs::value_type::first, std::placeholders::_1));

(Replace std::bind by boost::bind for non-C++11 code)

Software Craftsman
  • 2,999
  • 2
  • 31
  • 47
small_duck
  • 3,038
  • 20
  • 28