Tutorial :Is there an easy way to iterator over a static list of strings in C++



Question:

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.


Solution:1

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).


Solution:2

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++) {    ...  }  


Solution:3

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.


Solution:4

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!


Solution:5

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.


Solution:6

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.


Solution:7

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";  }  


Solution:8

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;  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »