Reversing strings in a vector using for_each and bind
Asked Answered
C

4

6

I was wandering how it's possible to reverese strings that are contained in a vector using a single for_each command just in one "simple" line.

Yea, I know it is easy with a custom functor, but I can't accept, that it can't be done using bind (at least I couldn't do it).

#include <vector>
#include <string>
#include <algorithm>

std::vector<std::string> v; 
v.push_back("abc");
v.push_back("12345");

std::for_each(v.begin(), v.end(), /*call std::reverse for each element*/);

Edit: Thanks a lot for those funtastic solutions. However, the solution for me was not to use the tr1::bind that comes with the Visual Studio 2008 feature pack/SP1. I don't know why it does not work like expected but that's the way it is (even MS admits that it's buggy). Maybe some hotfixes will help.

With boost::bind everything works like desired and is so easy (but sometimes relly messy:)). I really should have tried boost::bind in the first place...

Calabresi answered 30/9, 2009 at 9:38 Comment(5)
"Yes, I know it is easy to do with a wrench, but I can't accept that it can't be done using my hammer."Hypsometry
@R. Pate: I find that knowing how to use all the tools in my toolbox in many situations, even where another tool would have been better, is very important in improving expertise. My pet code is full of this stuff. In production code, I usually try to pick the best available (which I know, because I've experimented with them all).Petersham
I would also like to underline that using bind / lambdas are clearer: the definition of what is done is local which helps people reading it... if it is not too messy.Oeuvre
This question does have an "algorithm" tag because it covers handling of STL algorithms. (tag added again)Calabresi
added stl-algorithms instead :)Barty
P
13

std::for_each expects a unary function (or at least something with the typedefs of a unary function).

std::reverse<> is a binary function. It takes two iterators. It would be possible to bind it all together using boost::bind, but it would be a pretty horrible mess. Something like:

boost::bind(
    &std::reverse<std::string::iterator>,
        boost::bind(&std::string::begin, _1), 
        boost::bind(&std::string::end, _1))

Better, I think, would be to write a reusable function called reverse_range like so:

template <class Range>
void reverse_range(Range& range)
{
    std::reverse(range.begin(), range.end());
}

(probably with some metaprogramming to ensure that Range& isn't a double-reference)

And then use that in your for_each (after adapting it to be a unary function, of course).

std::for_each(v.begin(), v.end(),
    std::ptr_fun(&reverse_range<std::string>));

EDIT:

Because string::begin and string::end have both const and non-const variants, it is necessary to cast them (as litb discovered while I was off writing them to test my answer ... +1!). This makes it very verbose. Typedefs can make it a bit more sanitary, but to stick with the one-liner theme:

boost::bind(
    &std::reverse<std::string::iterator>,
    boost::bind(
        (std::string::iterator (std::string::*)())&std::string::begin, _1),
    boost::bind(
        (std::string::iterator (std::string::*)())&std::string::end, _1)
    )
);

Which just screams for refactoring.

Finally, because I'm bored, bonus points for C++0x:

std::for_each(v.begin(), v.end() [](std::string& s){ std::reverse(s); });

EDIT: boost::bind works just fine, no need for boost::lambda.

Petersham answered 30/9, 2009 at 9:59 Comment(3)
Nice! That's exactly what I wanted to know. Thank you!Calabresi
I tried and this one works: boost::bind(&std::reverse<std::string::iterator>, boost::bind((std::string::iterator(std::string::*)())&std::string::begin, _1),boost::bind((std::string::iterator(std::string::*)())&std::string::end, _1))Deputation
For these cases, i have get_nonconst / get_const functions in my toolbox: codepad.org/WI3w5xNhDeputation
O
5

You would have to roll your own reverse object:

struct Reverser
{
    void operator()(std::string& value) const
    {
        std::reverse(value.begin(),value.end());
    }
};

Now you can do it one line:

std::for_each(v.begin(), v.end(), Reverser());
Obstruction answered 30/9, 2009 at 9:58 Comment(0)
B
5

With Boost.Phoenix2:

std::for_each(v.begin(), v.end(), boost::phoenix::reverse(arg1));

To paraphrase mr-edd: measurably awesomer :)

Full example:

#include <boost/spirit/home/phoenix.hpp>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <iterator>

int main(void)
{

    using namespace boost::phoenix::arg_names; // for "arg1"

    std::vector<std::string> v;
    v.push_back("hello");
    v.push_back("world");
    std::for_each(v.begin(), v.end(), boost::phoenix::reverse(arg1));

    std::copy(v.begin(), v.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
}

prints:

olleh
dlrow
Bousquet answered 30/9, 2009 at 14:21 Comment(2)
+1 It also reminds me of my own personal wrapper for all STL algorithms, so they are callable on full containers: range::for_each(v, range::reverse<std::string>); - Simple :)Association
+1 Nice. Although I adore Boost.Spirit (just the sheer cleverness of the entire thing gives me the shivers, never mind that it compiles -- eventually -- down to something so tiny and fast), I haven't given Phoenix much time. Perhaps I should go sharpen that tool.Petersham
L
3

it could be also done with the BOOST_FOREACH macro:

BOOST_FOREACH( std::string& s, v )
    std::reverse( s.begin(), s.end() );
Loudmouthed answered 3/10, 2009 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.