Pregunta

To preface this question: I've been implementing various C++ utility functions and (when I have to) macros in a larger toolkit for my own use. Recently I had been making a variety of loop macros based on BOOST_FOREACH as well as iterable conscious functions.

Long story short, I ran into difficulty making an enumerate-loop which uses BOOST_FOREACH but with an additional parameter passed which is incremented on every iteration of the loop. This would function much like the enumerate command in Python which I find useful when looping over arbitrary iterators without adding a lot of boiler plate code. The macro takes the form like BOOST_FOREACH:

ENUMERATE_FOREACH(COUNT, VAR, COL)

and would effectively do this, but without relying on placing an unbalanced '}' to close (or other loop ending macro):

COUNT = 0; BOOST_FOREACH(VAR, COL) { COUNT++; ...Rest of loop...

Now before I get the accusations of trying to make a non-C++ operator in C++, I am fully aware that this is cramming a foreign concept into C++ and I'm not trying Pythonize C++ code. I am simply curious if it's cleanly possible to implement such a loop with known toolsets/boost without extreme dependencies. Having such a macro definition would eliminate a possible source of bugs for certain styles of looping where I need to count as I iterate and it disambiguates the purpose of the COUNT variable.

I thought about doing a template wrapper on the COL variable before it goes into BOOST_FOREACH, but the number of possibilities for COL makes that difficult/impossible with some combinations of iterable varibles without making different version of ENUMERATE_FOREACH and re-implementing a lot of BOOST_FOREACH -- a daunting and fool-worthy task without immense testing/time.

Doing a separate inline function could work, but then the syntax of looping becomes broken and I'm to more of a on-each style operator function passing situation (which I already have implemented).

This has left me with taking the final lines of foreach.hpp from boost's libraries and inserting my own increment operator on the additional argument. Then I become boost version dependent and worry about new updates (any syntax changes) to boost breaking my hacky custom macro.

A last option I thought about is to do a ENUMERATE_BEGIN and ENUMERATE_END to hide my iterator increment operation. This approach is more error prone than a single macro as the user must place two macros instead of one -- though that might be the only simple solution.

I tried looking around on SO and other sources to see if someone had tried this before without much luck. Hopefully someone has played with such an implementation concept successfully or has an idea about changing my approach. If there isn't a clean way of doing this I can just keep starting my loops with a count++ when I want to count. Again this is more of a curiosity and someone may propose that one of the ideas I quibbled about is a perfectly reasonable approach or as good as it will get.

¿Fue útil?

Solución 2

After reading Joachim's response I was mostly satisfied, but I tried to manipulate BOOST_FOREACH until I could at least do it in a very hacky manner, then I found I could actually implement the counter in a benign manner without rewriting the entire macro or doing some #undef #define statements on BOOST_FOREACH_NEXT.

I abuse the fact that BOOST_FOREACH has a (VAR = derefence(...); ...) statement and put a struct in between VAR and = so that I get (VAR = IncrementCountAndPassRHS = derefence(...); ...).

I haven't tested much (yet) for macro expansion problems, but I think it's safe inside the forloop.

EDIT Added updates to fix variable scope name overlap problems with multiple loops in the same line.

namespace loopnamespace {
template<typename T>
void incrementT(T *t) {
    (*t)++;
}

struct IncrementCounterPassthrough {
    bool checker;
    boost::function<void(void)> incrementer;

    template<typename Count>
    IncrementCounterPassthrough(Count& t) {
        t = -1;
        checker = true;
        incrementer = boost::bind(&incrementT<Count>, &t);
    }

    template<typename T>
    T& operator=(T& rhs) {
        incrementer();
        return rhs;
    }
};
}

#define ENUMERATE_FOREACH(COUNT, VAR, COL)                                                                   \
    for(::loopnamespace::IncrementCounterPassthrough BOOST_FOREACH_ID(_foreach_count_pass)(COUNT);           \
        BOOST_FOREACH_ID(_foreach_count_pass).checker; BOOST_FOREACH_ID(_foreach_count_pass).checker = false)\
    BOOST_FOREACH(VAR = BOOST_FOREACH_ID(_foreach_count_pass), COL)

Allows me to do:

std::string hello( "Hello, boost world!" );
unsigned int value;
ENUMERATE_FOREACH( value, char ch, hello ) {
   std::cout << ch << " => " << value << "\n";
}

to output:

H => 0
e => 1
l => 2
l => 3
o => 4
, => 5
  => 6
b => 7
o => 8
o => 9
s => 10
t => 11
  => 12
w => 13
o => 14
r => 15
l => 16
d => 17
! => 18

Otros consejos

You could solve it by having a four-argument version of ENUMERATE_FOREACH, where the last argument is a predicate to call.

Something like:

ENUMERATE_FOREACH(COUNT, VAR, COL, PRED)

and it would expand to something like

{
    COUNT = 0;
    BOOST_FOREACH(VAR, COL)
    {
        PRED(COUNT);
        COUNT++;
    }
}

The nice thing about the above is that the predicate can be a C++11 lambda function:

ENUMERATE_FOREACH(COUNT, VAR, COL,
                  [](int count){ cout << "count = " << count << '\n'; });

Edit: Another way, is to still use a fourth parameter, but let that parameter be actual code. For example:

ENUMERATE_FOREACH(COUNT, VAR, COL, do { std::cout << COUNT << '\n'; } while (0))

will expand to

{
    COUNT = 0;
    BOOST_FOREACH(VAR, COL)
    {
        do { std::cout << COUNT << '\n'; } while (0);
        COUNT++;
    }
}

It might be a little more messy than using e.g. C++11 lambdas.

Edit 2: If you have C++11, and can use lambdas, then you probably have the new range-based for loop as well, which means you can create a proper function instead, something like:

template<typename SeqT, typename PredT>
void for_each_count(SeqT seq, PredT pred)
{
    int count = 0;
    for (auto val : seq)
    {
        pred(val, count);
        count++;
    }
}

Usage:

std::vector<int> v = {1, 2, 3, 4};
for_each_count(v,
    [](int v, int c){
        std::cout << "value = " << v << ", count = " << c << '\n';
    });

The above will print

value = 1, count = 0
value = 2, count = 1
value = 3, count = 2
value = 4, count = 3
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top