Forcing evaluation order of `std::pair` constructors; a progress bar `struct` for a for-loop

109 Views Asked by At

I'm writing a program containing many long for-loops, and I want to add a progress bar indicator to each. To do this, I wrote struct ProgressBar to accomplish this. The interface is as follows:

struct ProgressBar {
    int start, end;  // starting and ending values of the loop variable
    const int &curr; // const reference to the loop variable

    /* Update the progress bar by inspecting the current value of "curr" */
    void update();

    /* Constructor; the reference "curr" is initialized in the member initializer list, as it must be */
    ProgressBar(int start, int end, const int &curr);

    ~ProgressBar() {
        cout << "100% done" << endl;
    }
};

And the idealized usage is

for (auto [i, pb] = pair{0, ProgressBar(0, 100, i)}; i <= 100; ++i) {
    pb.update();
    /* ... */
}

This does not work for at least two reasons:

  • ProgressBar(0, 100, i) causes a compiler error, since it depends on the loop variable i, whose type has not been deduced yet (error: use of ‘i’ before deduction of ‘auto’).
  • ProgressBar(0, 100, i) needs to be evaluated after i, but I believe the pair constructor, like all C++ functions, does not guarantee any particular order of evaluation of parameters of functions.

Any design ideas for what I should do instead?

2

There are 2 best solutions below

0
Martin York On BEST ANSWER

Why do you need two variables to count the loop? Can you not include the state as part of the progress bar.

Then you simply need a way of exposing the current state (either as some getter or simply check if it is done via boolean check) and a way of updating it. You could use update() as that method (or add ++ to your class).

#include <iostream>

class PB
{
    int beg;
    int end;
    int current;
    public:
        PB(int beg, int end)
            : beg(beg)
            , end(end)
            , current(beg)
        {}
        explicit operator bool()  {return current != end;}
        PB& operator++()          {++current;return *this;}
        void update()
        {
            std::cout << current << "\n";
        }
};

int main()
{
    for (auto pb = PB(1,100); pb; ++pb) {
        pb.update();
    }
}
0
Yakk - Adam Nevraumont On

Use a ranged-based for loop.

for (type x : expression) {
  // code
}

will expand to (if the suitable method exists):

{
  auto&& __range = expression;
  auto __it = __range.begin();
  auto __fence = __range.end();
  for (; __it != __fence; ++__it) {
    type x = *__it;
    // code
  }
}

If we want a pb variable separate from the iterator, we do this:

for (auto[x, pb] : ProgressBar(expression)) {
  // code
}

{
  auto&& __range = ProgessBar(expression);
  auto __it = __range.begin();
  auto __fence = __range.end();
  for (; __it != __fence; ++__it) {
    auto[x, pb] = *__it;
    // code
  }
}

so we just need * to return the appropriate stuff and ++ on the iterator to keep track of the stuff we like.

We can make this work for an arbitrary range-expression by storing it within the ProgressBar return value. We can use std::distance to work out how long the range is as well; note that this won't work with io type iterators. (I would take an optional manual distance argument).

template<class Range>
struct Progress {
  Range r;
  std::size_t count = -1;
  Progress(Range&& range, std::size_t c = -1):
    r(std::forward<Range>(range))
    count(c)
  {
    if (count == -1) {
      using std::begin; using std::end;
      count = std::distance( begin(r), end(r) );
    }
  }
  auto begin() const { /* todo */ }
  auto end() const { /* todo */ }
};

now we just need to write iterator adaptors for begin/end. These store a Range iterator, plus count information (current and max). ++ increases both the Range iterator and the value of current.

operator* on these adapters returns a pair of references to both * on the Range iterator, plus a progress report object with current/max in it.

If you want your progress report to have something else useful, you'd pass it through the ProgressBar function and Progress constructor, and store a reference or pointer to it in the iterators.

The big downside is that rolling your own iterator is annoying. Your advantage is that if you only want to support for(:) loops, you don't have to make an actual iterator -- you just have to properly handle !=, ++ and *.