|
Subscribe RSS Feed
Archive
Homepage
|
Wed, 12 Dec 2007
Using STL for_each to build delimited lists
If you're new to using STL some of the coolness might not be immediately obvious.
Here's an example of using a function object (called a 'functor') to build a delimited list.
Here's the scenario: You've got an arbitrary list of items in an STL container class.
You need a string form of the list. So you might think you need to write a loop and deal with inserting
the separator character correctly if there are only zero, or one, elements in the list.
STL provides a template called 'for_each' that will call a function for each of the
elements of the list. At first glance that doesn't seem to be very helpful in this scenario.
Using a function class instead of a standard C style function lets you do some real magic.
You can do it with one line of code and a class. Here's an example:
// build a list of elements
std::vector elements;
elements.push_back( std::string("one") );
elements.push_back( std::string("two") );
elements.push_back( std::string("three") );
elements.push_back( std::string("four") );
// convert to a pretty comma delimited list
std::string mylist = "MyList: ";
// build a list in one line of code!
for_each( elements.begin(), elements.end(), ListBuilder( &mylist, std::string(",") ) );
If you're familiar with SQL here's another example:
std::string sql = "INSERT INTO MyTable(";
for_each( args.begin(), args.end(), ListBuilder( &sql, std::string(",") ) );
sql += ")";
Here's the magic class that does all the work:
//---------------------------------------------------------------------------
// helper class to build delimited lists
//---------------------------------------------------------------------------
class ListBuilder
{
public:
ListBuilder( std::string* List, const std::string Separator ) : List(List), S(Separator)
{
};
std::string* List;
std::string D;
std::string S;
void operator()( std::string& elem )
{
*List += D + elem;
D = S;
}
};
Here's how the code works:
In place of the function there's a class instantiation:
for_each( elements.begin(), elements.end(), ListBuilder( &mylist, std::string(",") ) );
Before the for_each code is executed the program instantiates a ListBuilder class object:
ListBuilder( &mylist, std::string(",") )
Passed to the constructor of the object is the address of the list ("&mylist") and a string that is used
for the separator. Here's the constructor declaration:
ListBuilder( std::string* List, const std::string Separator ) : List(List), S(Separator)
The List address is stored in the class variable "List" by the initializer ( "List(List)" ).
The Separator string is stored in the class variable "S" by the initializer ( "S(Separator)" ).
As the for_each template iterates through the list it calls the parenthesis operator of the ListBuilder
class and passes each object in the list as a parameter. Here's the operator declaration:
void operator()( std::string& elem )
In the operator method it appends the separator and the element to the list:
*List += D + elem;
The class variable D is the empty string on the first call to this method.
It was NOT initialized by the class constructor.
For the first element it's just appended to the list with no delimiter.
It then sets the delimiter to the separator character:
D = S;
If the for_each template calls the operator more than once the separator is inserted
between the previous elements and the next one:
*List += D + elem;
In this example:
// build a list of elements
std::vector elements;
elements.push_back( std::string("one") );
elements.push_back( std::string("two") );
elements.push_back( std::string("three") );
elements.push_back( std::string("four") );
// convert to a pretty comma delimited list
std::string mylist = "MyList: ";
// build a list in one line of code!
for_each( elements.begin(), elements.end(), ListBuilder( &mylist, std::string(",") ) );
On the first call:
List is "one"
On the second call:
List is "one,two"
On the third call:
List is "one,two,three"
etc.
I hope this tutorial has been of use to you. Function classes can be a big help when using
STL algorithms. Writing functors works for the other STL algorithms but the class operator each algorithm
template uses may differ. If you look through the documentation for the algorithm it should mention
which operator that template uses.
Jay Sprenkle
posted at: 15:41 | path: | permanent link to this entry
|