Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I use std::get<0> in std::transform?

In trying to compile the following code which would copy a maps keys to a vector:

map<string, string> mss;
vector<string> vs;

transform(mss.begin(), mss.end(), back_inserter(vs), get<0>);

VS2013 can't distinguish which get is intended but this simpler usage works just fine:

vs.push_back(get<0>(*mss.begin()));

Specifying get<0, string, string> didn't help. What am I missing?

like image 940
screwnut Avatar asked Dec 20 '14 09:12

screwnut


2 Answers

There are many overloads of std::get, where, in addition, each is a function template itself, therefore the compiler can't tell which one you want at the call site where you request for the address of one of them. If you insist on using std::get, you'd need to use static_cast:

transform(mss.begin(), mss.end(), back_inserter(vs),
          static_cast<const map<string, string>::key_type&
                         (*)(map<string, string>::value_type&)>(std::get<0>)
                     );

Which will work as long as the type in static_cast matches the declaration of a possible function template's specialization given as the argument. Also, you shoudn't try to explicitly specify the template arguments of function templates like get<0, string, string> etc. - this is what the template argument deduction mechanism is for. Not only is the syntax ugly, but there can be other overloads added in the future breaking your compilation.

A much better alternative is to use a lambda expression:

transform(mss.begin(), mss.end(), back_inserter(vs),
          [](map<string, string>::value_type& p){ return p.first; });

or a generic lambda expression (C++14):

transform(mss.begin(), mss.end(), back_inserter(vs),
          [](auto& p){ return p.first; }); // or `return std::get<0>(p);`

or std::mem_fn which binds its argument to a given pointer to a data member or a member function:

#include <functional>

transform(mss.begin(), mss.end(), back_inserter(vs),
          mem_fn(&map<string, string>::value_type::first));
like image 128
Piotr Skotnicki Avatar answered Oct 19 '22 21:10

Piotr Skotnicki


The first member of the pair stored in map is const-qualified. So technically you need

get<0, const string, string>

But that does not limit the list of candidates to one unambiguous overload, since get is available in at least two versions: for const reference argument and for non-const reference argument.

You can choose one by using a cast

const string &(*g)(const pair<const string, string> &) = 
  get<0, const string, string>; 

or

typedef map<string, string> Map;

const Map::key_type &(*g)(const Map::value_type &) = 
  get<0, const Map::key_type, Map::mapped_type>; 

and then do

transform(mss.begin(), mss.end(), back_inserter(vs), g);
like image 32
AnT Avatar answered Oct 19 '22 22:10

AnT