Sentinel C++20. Writing our Sentinel

There is little talk about the concept of Sentinel, especially in the Russian-speaking space. Together with Yuri Vashinko, an experienced team lead and speaker of our C++ Developer course, today we will look at what Sentinel is and how to use it:

In every C++ developer (up to version 17), the concept of an iterator is firmly rooted, on which essentially all work with containers in the STL is built. But there are times when you have to abandon the use of algorithms for the sake of your own implementations, because they turn out to be more beneficial in terms of efficiency.

First, let's remind everyone of the well-known concepts. Containers provide us with iterators begin() And end()Where begin() points to the first element of the container, and end() to the “mythical” element, which is located “immediately behind” the last element. And when novice developers get acquainted with this, a lot of interesting things happen. Everyone represents first std::vectorand there you can imagine what is at the end. But if you ask, what will happen if this container is std::map – the answers will be different. And it can be difficult to explain that this element is essentially unreal.

For me personally, the iterator end() generally stands out from the hardcore, strictly typed and structured C++ language. At the time of the creation and implementation of this concept, this was a good decision, but times change and so do tasks. Let’s think about what we should do if we don’t need to process the entire array, but finish processing depending on the value inside the vector. Or, for example, we want to create an infinite array, that is, constantly updated. What, then, is considered a sign of the end? And for this kind of task you understand that there is nothing better than a passage in the form while(true) and exit according to the condition.

For example, let's take a problem where we need to convert the first word in a sentence to uppercase. (We’ll omit here why this might be needed. The variety of customer tasks is so great that the most unrealistic and sometimes stupid task today can become an urgent need tomorrow 🙂

Let's solve it using a loop while:

char introString[] = "Hello sentinel";
    int i = 0;
    while (introString[i]!=' ' && introString[i] != '\0')
    {
        introString[i] = toupper(introString[i]);
        i++;
    }

If you solve such a problem using stl algorithms, you will first have to find where ' ' is located, and then call the appropriate algorithm to traverse the data in this area, limiting it to iterators, which could potentially lead to a complete double traversal of the data.

std::vector<char> vectorArray = {'H','e','l','l','o',' ','s','e','n','t','i','n','e','l'};

    auto findex = std::find(vectorArray.begin(), vectorArray.end(),' ');

    std::transform(vectorArray.begin(), findex, vectorArray.begin(), to_uppercase);

The ' ' character here acts as a boundary when you need to stop processing the array.

And so we essentially came to the concept of sentinel (literally translated – “guard”). Sentinel is also called a processing completion flag or signal. And now let's imagine that we need to transfer not end()namely a certain sentinel, which can be simply compared to an iterator. What will this give us? First of all, we may not think about the final iterator in principle and the Sentinel type may not be an iterator at all. Let's rewrite our example using a separate Sentinel class:

class Sentinel {

public:
    bool operator==(std::vector<char>::iterator it) const {
        return *it == ' ';
    }
};
 std::vector<char> vectorArray = {'H','e','l','l','o',' ','s','e','n','t','i','n','e','l'};
    Sentinel sentinel;
    for (auto it = vectorArray.begin();it!=sentinel;++it)
    {
        *it = std::toupper(*it);

    }

This brings us to the concept of Sentinel as it is used in C++20.

This is how it is declared in the standard:

template< class S, class I >
    concept sentinel_for =        std::semiregular<S> &&        std::input_or_output_iterator<I> &&
        __WeaklyEqualityComparableWith<S, I>;

This is a concept that imposes a constraint, which means that the Sentinel can be an object that can be compared to an iterator.

Let's modify the Sentinel class a little so that it can stop processing not only at the ' ' character, but also at the end of the iterator

template <typename T>
struct Sentinel {
    bool operator==(T Iter) const {
        return Iter == ContainerEnd || *Iter == ' ';
    }
    T ContainerEnd;
};

Now, using the Sentinel class, we will solve the original problem.

    std::vector<char> vectorArray = { 'H','e','l','l','o',' ','s','e','n','t','i','n','e','l' };
    auto res = std::ranges::for_each(
        vectorArray.begin(), Sentinel{ vectorArray.end() }, [](auto &val) {
            val = toupper(val); 
        }
    );

Moreover, we can use this in other algorithms:

std::ranges::sort(vectorArray.begin(), Sentinel{ vectorArray.end() }, std::greater());

But that’s not all – our “mythical” end() can also act as a sentinel (although we already have this built into the Sentinel class), and this is what happens by default when we call, for example, the method ranges::sort()

    std::ranges::sort(vectorArray, std::greater());

But in this case end() takes on a completely different meaning; although it remains a kind of abstract concept, it can be freely replaced with something more tangible and connected specifically with real data.

And now, after we have examined this concept, it becomes obvious that using infinite arrays is not so difficult even with algorithms.

The introduction of Sentinel in C++20 greatly expands the ability to work with containers and iterators. Sentinel allows you to more flexibly manage sequence boundaries by replacing the “mythical” end() iterator with a more expressive object that does not have to be an iterator. This solution makes STL algorithms more efficient, especially when working with partial or infinite data structures. As a result, Sentinel not only simplifies code, but also opens up new opportunities for creating productive and understandable solutions in modern C++ applications.

Learn more about the nuances of using Sentinel and other features of the C++ language in our “C++ Developer” course, starting on October 28. Details – via the link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *