views:

722

answers:

8

It often happens that I need to iterate over a list of strings in my C++ code.

In languages like Perl, this is easy:

foreach my $x ("abc", "xyz", "123") {.... }

In the past, this is what I've done in C++

const char* strs[] = { "abc", "xyz", "123" };
for (int i=0; i<sizeof(strs)/sizeof(const char*); i++) {
   const char *str = strs[i];
   ...

If I already have an STL container, I can use BOOST_FOREACH

std::vector<std::string> strs;
BOOST_FOREACH(std::string str, strs) {
   ...

I've tried to create a macro to combine all these concepts but without success.

I'd like to be able to write code like this:

SPECIAL_STRING_FOREACH(const char* str, {"abc", "xyz", "123"}) {
   ...
}

Surely someone's cooked this up before.

A: 

Instead of this:

for (int i=0; i<sizeof(strs)/sizeof(const char*); i++)
{
    const char *str = strs[i];
    ...

Consider this:

for (const char *ptr = strs[0],
    *end = strs[sizeof(strs)/sizeof(const char*)];
    ptr < end; ++ptr)
{
    ...

You might find this form easier to macro-ize; in any case the ptr variable simulates an iterator.

Surely someone's cooked this up before.

I doubt they should. A for loop is idiomatic and easy to read (especially if you know the size of the array), user-defined macros are non-standard.

ChrisW
I prefer "sizeof(strs)/sizeof(*strs)" as this will prevent problems if you change the data type.
Richard Corden
That's true (though I'd say "sizeof(strs)/sizeof(strs[0])"); I was copy-and-pasting his implementation without reading it well. I'd also name that, e.g. "const size_t sizeof_strs = ...etc..." on the line immediately after the array definition, so that I can use the const identifier instead of the long expression in subsequent statements such as the for loop.
ChrisW
+4  A: 

Something like this:


void func( const char* s ) { /* ... */ }

const char* array[] = { "abc", "xyz", "123" };
std::for_each( array, array + 3, func );

You might also want to take a look at boost::array.

Nikolai N Fetissov
I) Please give code for calculating the 3! II) Inefficent because of the function pointer indirection! III) No advantage over boost::foreach or regular C++ loops!
Dario
Oh, come on, look at other answers for calculating static array length, function could be replaced with a functor instance, advantage is not having to include Boost headers, i.e. working with standard library only, no advantage over C++ loops, since, well, for_each IS a C++ loop.
Nikolai N Fetissov
+2  A: 

Cou could use the va_arg hack to create a function that returns an iterable collection (note that it's really a hack!)

The new C++-standards (C++0x) will provide a more convenient way of initialization (initializer lists)

Another possiblity would be using boost::assignment in combination with FOREACH.

Note that BOOST::FOREACH is applicable to arrays too!

Dario
+1 for mentioning C++0x
lothar
+4  A: 

Note that dealing with C array of string is easier if you mark the end of the array:

const char* strs[] = { "abc", "xyz", "123", NULL };
for (int i=0; strs[i] != NULL  i++) {
  ...
}
anon
A: 

This should do it (I didn't test it, so there might be some typos)

#define STR_ARRAY_FOREACH(I) const char* Items[] = I; for( const char *item = Items[0], *end = strs[ sizeof( Items ) / sizeof( const char* ) ]; item < end; ++item )

Then use item in the loop:

STR_ARRAY_FOREACH({ "abc", "xyz", "123" })
{
    cout << item << "\n";
}
Jon Benedicto
The preprocessor doesn't like the list.A simple test:#define M1(L) LM1({1,2,3,4})I get this from g++macro "M1" passed 4 arguments, but takes just 1. I don't know enough about writing macro defines to fix this.
mmccoo
+1  A: 

Making a macro that returns the size of the array helps here.

#define N_ELEMS(a) (sizeof(a) / sizeof((a)[0]))

Then your original code doesn't look so bad.

for(int i = 0; i < N_ELEMS(strs); ++i) {
  ...
}

It's a good idiom for iterating over any static array, not just arrays of strings.

Paul Hankin
+8  A: 

Here is my attempt at it. Sadly it relies on variadic macros which is a C99/C++1x feature. But works in GCC.

#include <boost/foreach.hpp>
#include <boost/type_traits.hpp>
#include <iostream>

#define SEQ_FOR_EACH(D, ...)                                        \
    if(bool c = false) ; else                                       \
        for(boost::remove_reference<boost::function_traits<void(D)> \
                ::arg1_type>::type _t[] = __VA_ARGS__;              \
            !c; c = true)                                           \
            BOOST_FOREACH(D, _t)

int main() {
    SEQ_FOR_EACH(std::string &v, { "hello", "doctor" }) {
        std::cout << v << std::endl;
    }
}

Note that you can also iterate with a reference variable, to avoid useless copying. Here is one using boost.preprocessor and the (a)(b)... syntax, compiling down to the same code after pre-processing stage.

#define SEQ_FOR_EACH(D, SEQ)                                          \
    if(bool c = false) ; else                                         \
        for(boost::remove_reference<boost::function_traits<void(D)>   \
                ::arg1_type>::type _t[] = { BOOST_PP_SEQ_ENUM(SEQ) }; \
            !c; c = true)                                             \
            BOOST_FOREACH(D, _t)

int main() {
    SEQ_FOR_EACH(std::string &v, ("hello")("doctor")) {
        std::cout << v << std::endl;
    }
}

The trick is to assemble a function type that has as parameter the enumeration variable, and getting the type of that parameter. Then boost::remove_reference will remove any reference. First version used boost::decay. But it would also convert arrays into pointers, which i found is not what is wanted sometimes. The resulting type is then used as the array element type.

For use in templates where the enumerator variable has a dependent type, you will have to use another macro which puts typename before boost::remove_reference and boost::function_traits. Could name it SEQ_FOR_EACH_D (D == dependent).

Johannes Schaub - litb
+1 for heroic effort :)
Nikolai N Fetissov
A: 

I'd try just using BOOST_FOREACH on the array directly. The documentation appears to think it would work.

In fact it does. It has to be two lines, but it works:

const char * myarray[] = {"abc", "xyz", "123"};
BOOST_FOREACH (const char *str, myarray) {
  std::cout << "Hello " << str << std::endl;
}
Trey Jackson