Dmitri Shuralyov
Dmitri Shuralyov

Reputation: 1042

How to refactor this while loop to get rid of "continue"?

I have a while (!Queue.empty()) loop that processes a queue of elements. There are a series of pattern matchers going from highest-priority to lowest-priority order. When a pattern is matched, the corresponding element is removed from the queue, and matching is restarted from the top (so that the highest-priority matchers get a chance to act first).

So right now it looks something like this (a simplified version):

while (!Queue.empty())
{
    auto & Element = *Queue.begin();

    if (MatchesPatternA(Element)) {    // Highest priority, since it's first
        // Act on it
        // Remove Element from queue
        continue;
    }
    if (MatchesPatternB(Element)) {
        // Act on it
        // Remove Element from queue
        continue;
    }
    if (MatchesPatternC(Element)) {    // Lowest priority, since it's last
        // Act on it
        // Remove Element from queue
        continue;
    }

    // If we got this far, that means no pattern was matched, so
    // Remove Element from queue
}

This works, but I want to refactor this loop in some way to remove the use of the keyword continue.

Why? Because if I want to outsource a pattern matching to an external function, it obviously breaks. E.g.

void ExternalMatching(...)
{
    if (MatchesPatternB(Element)) {
        // Act on it
        // Remove Element from queue
        continue;     // This won't work here
    }
}

while (!Queue.empty())
{
    auto & Element = *Queue.begin();

    if (MatchesPatternA(Element)) {
        // Act on it
        // Remove Element from queue
        continue;
    }
    ExternalMatching(...);
    if (MatchesPatternC(Element)) {
        // Act on it
        // Remove Element from queue
        continue;
    }

    // If we got this far, that means no pattern was matched, so
    // Remove Element from queue
}

I don't want to have to write repetitive if statements like if (ExternalMatching(...)) { ... continue; }, I'd rather find a cleaner way to express this logic.

This simplified example might make it seem like a good idea to make pattern matching more general rather than having distinct MatchesPatternA, MatchesPatternB, MatchesPatternC, etc. functions. But in my situation the patterns are quite complicated, and I'm not quite ready to generalize them yet. So I want to keep that part as is, separate functions.

Any elegant ideas? Thank you!

Upvotes: 1

Views: 1241

Answers (6)

Dmitri Shuralyov
Dmitri Shuralyov

Reputation: 1042

Ok, I ended up rewriting the loop more akin to this.

Huge thanks and credit goes to Yuushi, dasblinkenlight, David Rodríguez for their help; this answer is based on a combination of their answers.

bool ExternalMatching(...)
{
    bool Match;

    if ((Match = MatchesPatternX(Element))) {
        // Act on it
    } else if ((Match = MatchesPatternY(Element))) {
        // Act on it
    }

    return Match;
}

while (!Queue.empty())
{
    auto & Element = Queue.front();

    if (MatchesPatternA(Element)) {    // Highest priority, since it's first
        // Act on it
    } else if (MatchesPatternB(Element)) {
        // Act on it
    } else if (ExternalMatching(...)) {
    } else if (MatchesPatternC(Element)) {    // Lowest priority, since it's last
        // Act on it
    }

    // Remove Element from queue
}

Now, I know there's further room for improvement, see answers of Mateusz Pusz and Michael Sh. However, this is good enough to answer my original question, and it'll do for now. I'll consider improving it in the future.

If you're curious to see the real code (non-simplified version), see here:

https://github.com/shurcooL/Conception/blob/38f731ccc199d5391f46d8fce3cf9a9092f38c65/src/App.cpp#L592

Thanks everyone again!

Upvotes: 1

Mateusz Pusz
Mateusz Pusz

Reputation: 1393

If you have access to C++11 I would like to suggest another solution. Basicaly I created a container of handlers and actions that can be adjusted in runtime. It may be a pro or con for your design depending on your requirements. Here it is:

#include <functional>

typedef std::pair<std::function<bool(const ElementType &)>,
                  std::function<void(ElementType &)> > HandlerData;
typedef std::vector<HandlerData> HandlerList;


HandlerList get_handlers()
{
  HandlerList handlers;
  handlers.emplace_back([](const ElementType &el){ return MatchesPatternA(el); },
                        [](ElementType &el){ /* Action */ });
  handlers.emplace_back([](const ElementType &el){ return MatchesPatternB(el); },
                        [](ElementType &el){ /* Action */ });
  handlers.emplace_back([](const ElementType &el){ return MatchesPatternC(el); },
                        [](ElementType &el){ /* Action */ });
  return handlers;
}


int main()
{
  auto handlers = get_handlers();
  while(!Queue.empty()) {
    auto &Element = *Queue.begin();

    for(auto &h : handlers) {
      // check if handler matches the element
      if(h.first(Element)) {
        // act on element
        h.second(Element);
        break;
      }
    }

    // remove element
    Queue.pop_front();
  }
}

Upvotes: 3

Michael Shmalko
Michael Shmalko

Reputation: 710

Consider an interface:

class IMatchPattern
{
public:
    virtual bool MatchesPattern(const Element& e) = 0;
};

Then you can organize a container of objects implementing IMatchPattern, to allow for iterative access to each pattern match method.

Upvotes: 2

Chubsdad
Chubsdad

Reputation: 25497

I would like to suggest a Factory function that would take the Element and create an appropriate handler and return the interface pointer to the handler.

while (!Queue.empty())
{
    auto & Element = *Queue.begin();
    // get the appropriate handler object pointer e.g.
    IPatternHandler *handler = Factory.GetHandler(Element);
    handler->handle();
    // clean up handler appropriately
}

Upvotes: 0

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

You can change your ExternalMatching to return bool, indicating that the processing has been done. This way the caller would be able to continue evaluating if necessary:

bool ExternalMatching(...)
{
    if (MatchesPatternB(Element) {
        // Act on it
        // Remove Element from queue
        return true;
    }
    return false;
}

Now you can call it like this:

if (ExternalMatchin1(...)) continue;
if (ExternalMatchin2(...)) continue;
...
if (ExternalMatchingN(...)) continue;

Upvotes: 1

I would recommend using a function that does the pattern matching (but does not act on the result) and then a set of functions that act on the different options:

enum EventType {
   A, B, C //, D, ...
};

while (!queue.empty()) {
   auto & event = queue.front();
   EventType e = eventType(event); // Internally does MatchesPattern*
                                   // and returns the match
   switch (e) {
   case A:
      processA(event);
      break;
   case B:
      processB(event);

This way you clearly separate the matching from the processing, the loop is just a simple dispatcher

Upvotes: 3

Related Questions