📜 ⬆️ ⬇️

Convenient C ++ enumerator

All of us, C ++ programmers, undoubtedly love STL. Indeed, without it, many things would have to be written with your own hands. But sometimes STL causes pain and suffering. Recently, I was faced with the fact that a typical solution for standard algorithms, taking two iterators, first and last, turned out to be inconvenient in my simple project.

Please do not judge me, since all that you see below is just an attempt to deal with the complexity in your project and was done under a strong desire for the subjective beauty of the code.

For example, the task is to walk through the container with the elements. In this case, the behavior of the program, of course, will vary from element to element, depending on their values. Easy? It would seem that something complicated, but let's consider this situation with the usual vector of lines. Here is our function that performs this task:

void Action(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... } 

But as we remember, with a certain value of the current element, the behavior should change. There will be a lot of such values, and therefore there will be many variants of different behavior, so we will not produce if-else cloths in the traversal cycle, but create a separate function. What could be easier than:
')
 void ActionTwo(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... } void Action(std::vector<std::string>::const_iterator curr, const std::vector<std::string>::const_iterator &last) { if (curr == last) { return; } // ... if (*curr == ANY_VALUE) { ActionTwo(curr + 1, last); } // ... } 

And now we need to implement another behavior, for another value of the current position. I think you can not continue, and so clearly what this leads to. We are always forced to transfer current and last positions. Creating a common class for functions will not solve the problem, since the current value will still need to be compared with the last one, which means you will have to pass std :: vector <std :: string> :: const_iterator & last as a separate constant.

And then I remembered how conveniently iterator-enumerators were made in LINQ and Java, because in our case, pointer arithmetic is not required, using the + = and - = operators for std :: vector <std :: string> :: const_iterator curr. All we need is the ability to conveniently bypass the container by changing the behavior depending on the values. Next you will see my bike for LINQ-like enumerators in C ++.

Create a .h file for our experiment and define the namespace Dq in it, this will be the name for the mini library.

 #pragma once namespace Dq { //   : Container  ..Args    STL     . template <template <typename...> typename Container, typename ...Args> //  - class Enumerator { private: //    Container<Args...> &List = Container<Args...>(); //       typename Container<Args...>::iterator Position = List.begin() - 1; public: //             bool MoveNext() { return ++Position != List.end(); } //      typename Container<Args...>::value_type &operator*() const { return *Position; } //        void Reset() { Position = List.begin() - 1; } //      explicit Enumerator(const Container<Args...> &cont) : List(cont) {} }; } 

Already better, now you can pass one enumerator argument to the desired function. In C ++ 17, such an enumerator is very simple to create and use:

 std::vector values { 0, 1, 2, 3, 4, 5 }; Dq::Enumerator i(values); 

In the meantime, the standard is not finally approved, we will write an auxiliary function:

 namespace Dq { //       template <template <typename...> typename Container, typename ...Args> Enumerator<Container, Args...> GetEnumerator(const Container<Args...> &cont) { return Enumerator<Container, Args...>(cont); } } 

And we will use it like this:

 std::vector<int> values{ 0, 1, 2, 3, 4, 5 }; auto i = Dq::GetEnumerator(values); 

Armed with an enumerator, here's how to rewrite our first example:

 void ActionTwo(auto &position) { if (!position.MoveNext()) { return; } // ... } void Action(auto &position) { if (!position.MoveNext()) { return; } // ... if (*position == ANY_VALUE) { ActionTwo(); } // ... } 

For me, this solution is much more beautiful and more convenient for everyday tasks than the fuss with STL iterators. Now about the small flaw, such an iterator will not work for standard algorithms. But fortunately this is easily fixable, just add methods to the Enumerator class.

 template <template <typename...> typename Container, typename ...Args> typename Container<Args...>::iterator Enumerator<Container, Args...>::CurrentPostion() const { return Position; } 

and

 template <template <typename...> typename Container, typename ...Args> typename Container<Args...>::iterator Enumerator<Container, Args...>::LastPostion() const { return List.end(); } 

The entire code can be seen in one file on GitHub .

Let's sum up:

Now we have a convenient enumerator wrapper for standard iterators compatible with STL algorithms. Life has become a little more fun, global entropy due to the cost of writing an article has increased, and the objective utility of my bike can be judged by you).

Source: https://habr.com/ru/post/324808/


All Articles