Tag Archives: Range

Hi !
Today I am not going to talk about rendering. This article will deal with expressiveness in C++. Expressiveness? Yes, but about containers, range, and iterators.
If you want to know more about writing expressive code in C++, I advise you to go on fluentcpp.
If you want to know more about range, I advise you to take a look at the Range V3 written by Eric Niebler.
The code you will see may not be the most optimized, but it gives one idea behind what ranges are and how to implement it.

Introduction

How could we define a Range ?

The objective

Prior to defining what a Range is, we are going to see what Range let us do.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

intmain()

{

std::list<int>list;

std::vector<float>vector={5.0,4.0,3.0,2.0,1.0,0.0};

list<<10<<9<<8<<7<<6<<vector;

// list = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

auto listFiltered=list|superiorThan(4)|multiplyBy(3);

// type of listFiltered = Range<You do not want to know lol>

// listFiltered = [10, 9, 8, 7, 6, 5] -> 30, 27, 24, 21, 18, 15

auto listSorted=Range::sort(listFiltered|superiorThan(23));

// type of listSorted is vector, else use Range::sort<std::list>

// listSorted = [30, 27, 24] -> 24, 27, 30

std::cout<<list<<listFiltered<<listSorted;

return0;

}

Isn’t it amazing to write things like that? Okay for direct operation inside the container, it could be better in two ways:

It is not “easy” to read if you want to compose operation : (unique(sort(range)) is less readable than range | sort | unique in my opinion. But it is juste one “optimisation” to do :).

It may be not optimized since sort returns a Container : here a vector, and build also.

Smart Iterator in details

Lazy Initialization

This statement tells us “If the result of the operation is not needed right now, there is no need to compute it”. To be simple, the operation will be done when we will need to get the result. With iterator, it could be done when you dereference it for instance.

Different types of smart iterator

Filter iterator

This iterator will jump a lot of values that do not respect a predicate. For example, if you want only the odd values of your container, when you increment the iterator, it will advance until the next odd value and will skip all the even ones.

Transform iterator

This iterator will dereference the iterator, apply a function to the dereferenced value and returns the result.

Implementation

Basics

Here we are going to implement our own iterator class. This class must be templated twice times.
The first template argument is the iterator we want to iterate on. The second template argument is a tag that we use to perform a kind of tag dispatching.
Moreover, this iterator must behave as … an iterator !

So, we begin to write :

1

2

3

4

5

6

7

8

9

10

template<classIterator,classRangeIteratorTagStructure>

classRangeIterator{

Iterator mIt;

RangeIteratorTagStructure mTag;

public:

using iterator_category=typename Iterator::iterator_category;

using value_type=typename Iterator::value_type;

using difference_type=typename Iterator::difference_type;

using pointer=typename Iterator::pointer;

using reference=typename Iterator::reference;

One of above typename will fail if Iterator does not behave like one iterator.
The Tag has a constructor and can own several data (function / functor / lambda), other iterators(the end of the range?) or other things like that.

The iterator must respect the Open Closed Principle. That is why you must not implement the methods inside the class but outside (in a namespace detail for instance). We are going to see these methods later. To begin, we are going to stay focused on the RangeIterator class.

Forward iterator to go farther !

Again, simple code !

1

2

3

4

RangeIterator&operator++(){

detail::RangeIterator::increment(mIt,mTag);

return*this;

}

Backward iterator, to send you in hell !

Okay, now as promised, we are going to see how beautiful C++ templates are. If you don’t want to be driven crazy, I advise you to stop to read here.
So, we saw that not all iterators have the “backward” range. The idea is to enable this feature ONLY if the iterator (the first template argument) supports it also.
It is the moment to reuse SFINAE (the first time was for the “is_range” structure we saw above).
We are going to use the type_trait std::enable_if<Expr, type>.
How to do that?

Thanks to overloading via tag dispatching this function should (must??) be called without any issues (actually you hope :p).

However, if you want to use several files (thing that I only can to advise you), you cannot do it by this way but specialize your templates. But you cannot partially specialize template function. The idea is to use functor!

Warning : Don’t forget to take care about rvalue references to be easy to use !

Conclusion

So this article presents a new way to deal with containers. It allows more readable code and take a functional approach. There is a lot of things to learn about it, so don’t stop your learning here. Try to use one of the library below, try to develop yours. Try to learn a functional language and … Have fun !!!!

I hope that you liked this article. It is my first article that discuss only C++. It may contains a lot of errors, if you find one or have any problems, do not forget to tell me!