Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 533
mmccoo Avatar asked Nov 29 '22 12:11

mmccoo


1 Answers

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

like image 72
Johannes Schaub - litb Avatar answered Dec 05 '22 14:12

Johannes Schaub - litb