2008-07-05

STL rocks

I've just been writing a small article on the "gems of STL" and found that I really like what I'm seeing. A personal favorite is the transform operation (found in the functional header file). For example, lets do some adding.
#include <functional>
#include <list>

...

{
std::list<int> list;
list.push_back( ... ); // Populate

std::transform( list.begin(), list.end(), list.begin(), std::bind2nd( std::plus<int>(), 42 ) );
}
So, the transform method takes three iterators and an functor. The operators are, from the left, the starting point of the input, the end point of the input and the starting point of the output. The function simply specifies what to do with each list entry to create the output. In this case, std::plus takes two arguments, but we bind the second argument to 2. What this does is that it adds 42 to every item in the list (between begin and end), and replaces the original items (the results are placed from begin and onwards).

If you want the results to end up in another list, just use the back_inserter magical interator and point it to your results list.
{
std::list<int> list, res;
list.push_back( ... ); // Populate

std::transform( list.begin(), list.end(), std::back_inserter( res ), std::bind2nd( std::plus<int>(), 42 ) );
}

Quite readable and really cool code if you ask me.

12 Comments:

At 2:42 PM, Blogger Lee_B said...

Granted, that's a nice bit of flexibility from a library rather than built into the core.

BUT... Dude, this is horrible syntax!

In python, you would do something like:

lst = [ 1,2,3 ] # declare and populate
lst = [ i + 42 for i in lst ] # add 42 to all elements

These for statements do work on any class that provides iterators.


Or, if you really need a function:

l, r = [], [] # declare two lists
l.append(...) # populate

def myop(a):
return a + 42

r = [ myop(i) for i in l ] # generate new list and populate from call results

Obviously, you have callable classes and all the other goodies you need. Ruby and D would make it similarly easy, I think.

I used to like compiled languages, and especially C. C++ was OK before it started getting crazy with STL and all. These days though, I tend to think it should just die.

 
At 2:53 PM, Blogger zwabel said...

Well, I like STL and its generality, but at least for this simple case I find this much more readable ;) :

foreach(int i, list)
result << i + 42;

 
At 3:22 PM, Anonymous Thiago Macieira said...

You can also use std::transform and all of the STL algorithms on Qt containers too. That just works :-)

Anyways, the code would be more readable with a lambda function (C++0x feature)

 
At 3:36 PM, Blogger FeepingCreature said...

Hah. You call that readable?

Here's the equivalent code in D, another C-like, compiled language, using the Tools extension library.

list = list /map/ (int i) { return i+42; };

Or alternatively

auto res = list /map/ ex!("a -> a+42");

--FeepingCreature, D fanboy ^^

 
At 5:09 PM, Anonymous Anonymous said...

In haskell you could do it with

map (+2) list

or, using list comprehension,

[x+2 | x <- list]

 
At 5:23 PM, Anonymous Robert said...

"Anyways, the code would be more readable with a lambda function (C++0x feature)"

Unfortunately, for every piece of crazy syntax C++0x fixes, it adds two new pieces of random keyword/punctuation madness.

 
At 7:53 PM, Blogger rodrigo said...

I think is a great post about a good way of using STL for C++. Most of the comments above are about other languages... that wasn't the idea of the post! For C++ and for STL in particular, is not so "horrible syntax" after all.

 
At 7:57 PM, Anonymous Anonymous said...

I'm quite amused by you definition of readable :) Sure technically this is cool, but reading such code is IMHO just horrible, if you're someone who doesn't use the most obscure STL features on a daily basis. back_inserter?? bind2nd?? .. erm, sure... And after what I've read about C++0x, this will get much worse...

 
At 8:37 PM, Anonymous litb (find me in ##c++) said...

with c++0x you will have serveral options:

std::transform( list.begin(), list.end(), list.begin(), [](int i){ return i+42; });

for(int& i : list) i += 42;

std::for_each( list.begin(), list.end(),
[](int & i) { i += 42; });

std::transform( list.begin(), list.end(), list.begin(), bind(plus<int<(), 42, _1));

 
At 9:01 PM, Anonymous litb said...

and... btw, initializer-lists will allow you to init your list like this:
std::list<int> list = { 1, 2, 3, 4 };

alternatively, of course, imagine:
for(int i : { 1, 2, 3, 4 }) list.push_back(i);

and finally list.push_back(1, 2, 3, 4, 5);
(yeah, you get type-safe variadic (template)-functions/classes!)

Variadic templates, (limited) initializer lists, rvalue references and move semantics can be already tested with GCC4.4 trunk. All this but initializer-lists can be already tested wth gcc4.3 given the --std=c++0x option

 
At 10:14 PM, Anonymous Anonymous said...

If you like that (and the concept is damn neat) you should look into specifically designed functional languages, assuming you haven't already. They're designed so that the syntax for that kind of thing is the best it can be.

My favourite's Haskell, but that might be a bit of a culture-shock compared to C++. Ocaml might be a good one to start with, or some lisp if you're not put off by the style.

 
At 5:05 AM, Blogger Vexorian said...

I personally find it Hilarius that both the python and the D fans posted code much less readable than the original post's. Apparently their ultra readable codes depend on quite obscure or overrated syntax additions, [i+42 for i in lst] and not to mention the D example: list /map/ ex!("a -> a+42"); So D adds a trillion more operators? fun...

So the STL implements this and didn't require the compiler to add random syntax candy. I find it great. If you have doubts, most of the apparent difficulty to read comes from not declaring the functor manually.

But if you would really like to make a convincing python example:

>>> V=[1,2,3]
>>> map(lambda(x):x+42,V)

 

Post a Comment

<< Home