It's been a while but I had another look at this. Since we now support c++-20 concepts, they could be used to make the code a bit nicer and solve the problem mentioned below in a different way:
Instead of proxy classes advertising the class that they iterate over with a typedef `ItemType`, there now is a concept `indexable` that ensures that a class supports `c.size()` and `c[i]`. This is all we need to implement the iterator and the begin/end methods. One potential downside is that our iterator returns everything by value, not by reference. This is fine for our proxy classes where the returned values that we iterate over are light-weight proxies themselves. It could become an issue if ProxyIterator is used for a non-proxy class. For example, imagine ProxyIterator being used for a vector containing expensive-to-copy objects. Then a for loop over this vector would copy all objects into the loop variable. This doesn't happen because vector has its own begin/end methods that are preferred over ours, but it could be a danger with custom container classes. We could adapt the concept to only allow classes where operator[] returns by value anyway. What do you think?
Another snag is the uncrustify configuration. Uncrustify doesn't support C++-20 features yet, so the code formatting of the concepts has several mistakes that we cannot write in a nicer way. The option mod_remove_extra_semicolon also removes a non-optional semicolon from the concept, breaking the build. In the PR, this option is disabled but the remaining options are left as they were.
Finally, the issue with State, mentioned before, is that State now does have a size method and an index operator with integers, so it does technically satisfy the concept `indexable`. With our ProxyIterator, this now automatically generates begin/end methods that return a proxy iterator. Unfortunately this uses ints so it would only work in for loops like `for (int value : state)` which is not what we want. We want a different iterator that works in loops like `for (FactProxy fact : state)` but we cannot overload `begin(const State &)` to return two different iterators. This is solved now with a template specialization `ProxyIterator<State>` that uses variable proxies internally. Another option would have been to implement the iterator as a stand-alone class, not related to ProxyIterator and just disable ProxyIterator<State>. I'm not sure what is better here. We also have `VariablesProxy.get_facts()` which returns a `FactsProxy`, a class that is only there to allow iteration and doesn't match our new concept. All of this is a bit messy and I'd appreciate some feedback on it.
|