views:

327

answers:

8

I started using stl containers because they came in very handy when I needed functionality of a list, set and map and had nothing else available in my programming environment. I did not care much about the ideas behind it. STL documentations were only interesting up to the point where it came to functions, etc. Then I skipped reading and just used the containers.

But yesterday, still being relaxed from my holidays, I just gave it a try and wanted to go a bit more the stl way. So I used the transform function (can I have a little bit of applause for me, thank you).

From an academic point of view it really looked interesting and it worked. But the thing that boroughs me is that if you intensify the use of those functions, you need 10ks of helper classes for mostly everything you want to do in your code. The hole logic of the program is sliced in tiny pieces. This slicing is not the result of god coding habits. It's just a technical need. Something, that makes my life probably harder not easier.

And I learned the hard way, that you should always choose the simplest approach that solves the problem at hand. And I can't see what, for example, the for_each function is doing for me that justifies the use of a helper class over several simple lines of code that sit inside a normal loop so that everybody can see what is going on.

I would like to know, what you are thinking about my concerns? Did you see it like I do when you started working this way and have changed your mind when you got used to it? Are there benefits that I overlooked? Or do you just ignore this stuff as I did (and will go an doing it, probably).

Thanks.

PS: I know that there is a real for_each loop in boost. But I ignore it here since it is just a convenient way for my usual loops with iterators I guess.

A: 

These are indeed real concerns, and these are being addressed in the next version of the C++ standard ("C++0x") which should be published either at the end of this year or in 2011. That version of C++ introduces a notion called C++ lambdas which allow for one to construct simple anonymous functions within another function, which makes it very easy to accomplish what you want without breaking your code into tiny little pieces. Lambdas are (experimentally?) supported in GCC as of GCC 4.5.

Michael Aaron Safyan
Also in Visual Studio 2010.
DeadMG
+1  A: 

I guess the C++ comity has the same concerns. The to be validated new C++0x standard introduces lambdas. This new feature will enable you to use the algorithm while writing simple helper functions directly in the algorithm parameter list.

std::transform(in.begin(), int.end(), out.begin(), [](int a) { return ++a; })
Didier Trosset
+3  A: 

I find it most useful when used along with boost::bind and boost::lambda so that I don't have to write my own functor. This is just a tiny example:

class A
{
public:
    A() : m_n(0)
    {
    }

    void set(int n)
    {
        m_n = n;
    }

private:
    int m_n;
};

int main(){    

    using namespace boost::lambda;

    std::vector<A> a;
    a.push_back(A());
    a.push_back(A());

    std::for_each(a.begin(), a.end(), bind(&A::set, _1, 5));


    return 0;
}
Naveen
+5  A: 

The whole logic of the program is sliced in tiny pieces. This slicing is not the result of good coding habits. It's just a technical need. Something, that makes my life probably harder not easier.

You're right, to a certain extent. That's why the upcoming revision to the C++ standard will add lambda expressions, allowing you to do something like this:

std::for_each(vec.begin(), vec.end(), [&](int& val){val++;})

but I also think it is often a good coding habit to split up your code as currently required. You're effectively separating the code describing the operation you want to do, from the act of applying it to a sequence of values. It is some extra boilerplate code, and sometimes it's just annoying, but I think it also often leads to good, clean, code.

Doing the above today would look like this:

int incr(int& val) { return val+1}

// and at the call-site
std::for_each(vec.begin(), vec.end(), incr);

Instead of bloating up the call site with a complete loop, we have a single line describing:

  • which operation is performed (if it is named appropriately)
  • which elements are affected

so it's shorter, and conveys the same information as the loop, but more concisely. I think those are good things. The drawback is that we have to define the incr function elsewhere. And sometimes that's just not worth the effort, which is why lambdas are being added to the language.

jalf
I think in 60% of all cases the functor will contain not more then 3 lines of code and will probably not be reused somewhere else (no body will search for these to reuse them because names for those functors are getting messy real quick). In another 30% the code that is transferred to the functor is non-trivial but is unique in the code base with no need for refactoring it into an object. As I see it, there is a small number of occasions (<10%) where you have real benefits in regard to the quality of code by producing functors. But I'm working on a business application...
I agree with thomas-gies. I usually define my functors in the method using them since they're not used anywhere else.
yngvedh
there is a missing semicolon after return val+1 ;)
f4
Functors also get messy when you need to capture some of the variables in the calling method. I find that more often than not I need to capture a variable for functors.To capture variables, you need to declare appropriate members in the functor and you need to make sure these members are initialized in the calling method. This is both error prone and cumbersome and should really have been handled by the compiler. Of course, C++0x will fix this with lambdas.
yngvedh
@thomas-gies: true, but without using a functor, you also have to write the loop itself (which is another 2-3 lines of code). The function you're writing gets more cluttered. So just for the sake of simplifying and shortening the code you're writing, it may be worth it to pull the loop contents out into a separately defined functor. But yes, it is inconvenient. Sometimes *too* inconvenient. Which is why I'm happy that VC2010 supports lambdas ;)
jalf
FredOverflow
+2  A: 

You'll find disagreement among experts, but I'd say that for_each and transform are a bit of a distraction. The power of STL is in separating non-trivial algorithms from the data being operated on.

Boost's lambda library is definitely worth experimenting with to see how you get on with it. However, even if you find the syntax satisfactory, the awesome amount of machinery involved has disadvantages in terms of compile time and debug-ability.

My advice is use:

for (Range::const_iterator i = r.begin(), end = r.end(); i != end(); ++i)
{
   *out++ = ..   // for transform
}

instead of for_each and transform, but more importantly get familiar with the algorithms that are very useful: sort, unique, rotate to pick three at random.

James Hopkin
A: 

Those libraries like STL and Boost are complex also because they need to solve every need and work on any plateform.

As a user of these libraries -- you're not planning on remaking .NET are you? -- you can use their simplified goodies.

Here is possibly a simpler foreach from Boost I like to use:

BOOST_FOREACH(string& item in my_list)
{
    ...
}

Looks much neater and simpler than using .begin(), .end(), etc. and yet it works for pretty much any iteratable collection (not just arrays/vectors).

Wernight
+1  A: 

Local classes are a great feature to solve this. For example:

void IncreaseVector(std::vector<int>& v)
{
 class Increment
 {
 public:
  int operator()(int& i)
  {
   return ++i;
  }
 };

 std::for_each(v.begin(), v.end(), Increment());
}

IMO, this is way too much complexity for just an increment, and it'll be clearer to write it in the form of a regular plain for loop. But when the operation you want to perform over a sequence becomes mor complex. Then I find it useful to clearly separate the operation to be performed over each element from the actual loop sentence. If your functor name is properly chosen, code gets a descriptive plus.

Antonio Perez
This helps to solve the "where should I put the functor?" question. But if the class Increment needs information about the context you still need to declare members and a constructor etc. Or can this also be avoided by this approach? In ruby the blocks can access the stack of the context.
Antonio Perez
Have you compiled this? In C++ 98 local classes cannot be used as template arguments. In C++ 0x they can, but there we have lambdas.
Nemanja Trifunovic
It compiles right in MSVS 2008. Will try it later with gcc.
Antonio Perez
+2  A: 

Hi,

Incrementing a counter for each element of a sequence is not a good example for for_each.

If you look at better examples, you may find it makes the code much clearer to understand and use.

This is some code I wrote today:

// assume some SinkFactory class is defined
// and mapItr is an iterator of a std::map<int,std::vector<SinkFactory*> >

std::for_each(mapItr->second.begin(), mapItr->second.end(),
    checked_delete<SinkFactory>);

checked_delete is part of boost, but the implementation is trivial and looks like this:

template<typename T>
void checked_delete(T* pointer)
{
    delete pointer;
}

The alternative would have been to write this:

for(vector<SinkFactory>::iterator pSinkFactory = mapItr->second.begin();
    pSinkFactory != mapItr->second.end(); ++pSinkFactory)
    delete (*pSinkFactory);

More than that, once you have that checked_delete written once (or if you already use boost), you can delete pointers in any sequence aywhere, with the same code, without caring what types you're iterating over (that is, you don't have to declare vector<SinkFactory>::iterator pSinkFactory).

There is also a small performance improvement from the fact that with for_each the container.end() will be only called once, and potentially great performance improvements depending on the for_each implementation (it could be implemented differently depending on the iterator tag received).

Also, if you combine boost::bind with stl sequence algorithms you can make all kinds of fun stuff (see here: http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html#with_algorithms).

utnapistim