| Exploring Solution Spaces © Copyright 2003-2006, by C. Keith Ray | ||||||||||||||||||||||||
|
Archives
Subscribe |
2007.Mar.24 Sat
Work-Arounds for C++'s Lacking Block Syntax
Doing a simple operation on each item in container... In Smalltalk, an arbitrary block of code can be passed to a method of a container. In the example below, "[" and "]" delimit a block of code, which is passed to the method named "do:" of the class List.
testDo
| alist aLocal |
alist := List new.
"...add Item instances, etc. to alist skipped."
alist do: [ | item | item doSomething ].
In Ruby, a block of code can be passed to a method using "{" and "}" to delimit the code, or "do" and "end". The method in this case is named "each" and belongs to the List class.
def testEach
alist = List.new
# add Item instances, etc. to alist skipped.
alist.each { | item | item.doSomething }
end
In C++ the are no code-blocks that can be passed around like other objects. There are several alternatives which involve writing a function or a class and then... Passing the class as a type-specialization of a template, or passing an instance of a class to have a member function invoked, or passing a function name as a specialization of a template, or passing a function pointer or a passing a pointer-to-a-member-function and a pointer to an object. The first two examples here are using a function and a functor class with the for_each template function.
#include <vector>
#include <algorithm>
class Item
{
public:
// etc...
void doSomething();
};
void doSomething(Item& anItem)
{
anItem.doSomething();
}
struct Functor
{
void operator()(Item& anItem)
{
anItem.doSomething();
}
};
void testForEachWithFunctionAndFunctor()
{
vector<Item> alist;
// add Item instances, etc. skipped.
for_each(alist.begin(), alist.end(), doSomething);
for_each(alist.begin(), alist.end(), Functor());
}
One of the drawbacks of the function or functor approach is you can't easily refer to local variables, like the Smalltalk and Ruby examples can do. (Not shown above to keep everything simple.) And the function tends to end up at an inconvenient distance away from the code that uses it do the iteration. An alternative is to use a static function declared in a class, which you can define inside your function, but accessing local variables is still inconvenient. (And for some reason, a functor class defined inside a function doesn't compile. Anyone know why?)
#include <vector>
#include <algorithm>
class Item { ... };
void testForEachWithClassStatic()
{
vector<Item> alist;
// add Item instances, etc...
class Function
{
static void doSomething(Item& anItem)
{
anItem.doSomething();
}
};
for_each(alist.begin(), alist.end(), Function::doSomething);
}
/*
void testDoesntCompile()
{
struct FunctorInternal
{
void operator()(Item& anItem)
{
anItem.doSomething();
}
};
for_each(alist.begin(), alist.end(), FunctorInternal()); // error
}
*/
So if you want to refer to local variables easily, and have the code where you expect it, there's the old for-loop:
#include <vector>
#include <algorithm>
class Item { ... };
void testForLoop()
{
vector<Item> alist;
// add Item instances, etc...
for (vector<Item>::iterator it = alist.begin();
it != alist.end(); ++it )
{
it->doSomething();
}
}
However, that can get verbose if the types and variables involved have long names. The last alternative to simulate passing a bit of code around is to get the preprocessor involved. In these examples, a macro can avoid the explicit 'for' loop as long as the action to do in the loop is simple enough. (And you can still access local variables.)
#define APPLY4(vectorType,vectorName,iteratorName,Action) \
for ( vectorType::iterator iteratorName = vectorName.begin(); \
iteratorName != vectorName.end(); ++iteratorName) \
{ \
(Action); \
}
#define APPLY3(vectorName,iteratorName,Action) \
{ \
typedef typeof(vectorName) vectorType; \
typedef vectorType::iterator itType; \
for ( itType iteratorName = vectorName.begin(); \
iteratorName != vectorName.end(); ++iteratorName) \
{ \
(Action); \
} \
}
// NOTE: "typeof" used above is non-standard/non-portable.
// (it's in the version of gcc C++ that I'm using on MacOS X.)
void testApplyMacros()
{
vector<Item> alist;
// add Item instances, etc...
APPLY4(vector<Item>, alist, each, each->doSomething);
APPLY3(alist, each, each->doSomething);
}
By the way, the APPLY macros work on more than just vectors -- any container that implements "iterator". Making a APPLY_CONST macro that works with "const_iterator" should be trivial. |
|||||||||||||||||||||||