Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is std::map + std::tr1::bind + standard algorithms worthwhile?

This is a follow-up to my question from yesterday. I have Scott Meyers' warning about write-only code on my mind. I like the idea in principle of using standard algorithms to access the keys or values of a std::map, but the syntax required is a little baroque IMHO. Let's say I want to dump all the keys of a map to a vector. Given following declarations,

typedef std::map<int, int> MyMap;
MyMap m;
std::vector<int> v;

which code is more maintainable (i.e., potentially less confusing)?

Option #1:

std::transform(m.begin(),
               m.end(),
               std::back_inserter(v),
               std::tr1::bind(&MyMap::value_type::first, _1));

Option #2:

for (MyMap::iterator i = m.begin(); i != m.end(); ++i)
{
    v.push_back(i->first);
}

Option 1 is more standard library-ish but I have to mentally decompose it to understand what's going on. Option 2 seems easier to read at the expense of a possible small runtime penalty. I'm not hurting for CPU time so I'm leaning toward option 2. Does you guys agree? Is there a third option I should consider?

P.S. Over the course of writing this question I concluded that the best way (for my project) to read the keys of a std::map is to store them off in a side container and iterate over that. The maintainability question still stands though.

like image 400
Michael Kristofik Avatar asked Dec 17 '08 21:12

Michael Kristofik


6 Answers

Clarity always beats clever. Do what you can read later.

You're not alone in thinking that the standard code is a little obtuse. The next C++ standard will introduce lambda functions so you can write more legible code with the standard algorithms.

like image 52
Drew Dormann Avatar answered Nov 05 '22 20:11

Drew Dormann


The first is just as readable and maintainable as the second -- if you know what bind does. I've been working with Boost::Bind (essentially identical to std::tr1::bind) long enough that I have no trouble with it.

Once TR1 becomes part of the official standard, you can safely assume that any competent C++ programmer will understand it. Until then, it could pose some difficulty, but I always think of the long-term over the short-term.

like image 30
Head Geek Avatar answered Nov 05 '22 18:11

Head Geek


You forgot using namespace std::tr1::placeholders :P

To be honest, for simple algorithms like this, the latter code is probably easier to maintain. But I actually tend for the former (especially when C++1x gives us lambdas!) because it emphasizes a functional style of programming, which I personally prefer to the imperative style of using a loop.

It's really a different strokes thing; standard algorithms are most useful when they are either complex or generic, and this is neither.

Here's what it will look like with lambdas:

std::transform(m.begin(), m.end(), std::back_insterter(v),
               [](MyMap::value_type pair){ return pair.first; }
              );

And actually, there's another approach, which I would prefer but for its verbosity:

using std::tr1::bind;
using std::tr1::placeholders::_1;
std::for_each(m.begin(), m.end(),
              bind(&std::vector<int>::push_back, v,
                   bind(&MyMap::value_type::first, _1)
                  )
             );

And with lambdas (this is probably by and large the neatest and most explicit of all the options):

std::for_each(m.begin(), m.end(),
              [&v](MyMap::value_type pair){v.push_back(pair.first);}
             );
like image 37
coppro Avatar answered Nov 05 '22 20:11

coppro


I say go for 2)

To improve performance, you could get m.end() out of the loop and reserve the space in the vector.

Can't wait for C++0x and the range-based for loop; that would make your loop even better.

like image 31
Nemanja Trifunovic Avatar answered Nov 05 '22 18:11

Nemanja Trifunovic


Go with option #1, see Scott Meyers, Effective STL Item #43, page 181.

like image 28
paxos1977 Avatar answered Nov 05 '22 19:11

paxos1977


When I looked at your question yesterday it wasn't the bind (which I use a lot) that forced me to look twice to understand the code but the map::value_type::first which I haven't had occasion to use very often. Whilst I agree that "Clarity always beats clever", familiarity is required before clarity and you're not going to become familiar with styles you don't use...

I'd also say that while option 2 is clearer in terms of comprehending the intended purpose, it would hide a bug more easily (any error in option 1 is more likely to be visible at compile time).

like image 39
Patrick Avatar answered Nov 05 '22 18:11

Patrick