5 Methods to Enhance or Keep away from Loops in C++20…23

[ad_1]

On this quick article, I’ll present you many strategies to enhance uncooked loops. I’ll take an instance of a reverse iteration over a container after which remodel it to get probably higher code.

The loop expression is a necessary constructing block of programming. Whenever you iterate over a container in C++20, we now have the next choices:

  • Vary based mostly for loop with an initializer
  • Iterators
  • Algorithms
  • Ranges and views

See examples beneath:

0. A damaged loop

 

Let’s begin with the next code; we’d prefer to print a std::vector in reverse:

std::vector vec { 1, 2, 3, 4, 5, 6 };
for (auto i = vec.measurement() - 1; i >= 0; --i) {
    std::cout << i << ": " << vec[i] << 'n';
}

This code is damaged, as auto i shall be unsigned and can’t be “lower than 0”! This isn’t a breakthrough, and we mentioned this subject intimately in my different article: Decreasing Signed and Unsigned Mismatches with std::ssize() – C++ Tales.

The purpose right here is that such a uncooked loop provides a risk of entering into hassle. It’s simple to combine one thing and break the code.

We are able to repair it through the use of modulo 2 arithmetic:

std::vector vec { 1, 2, 3, 4, 5, 6 };
for (auto i = vec.measurement() - 1; i < vec.measurement(); --i) {
    std::cout << i << ": " << vec[i] << 'n';
}

And beneath, there’s even a greater resolution:

1. Fixing with secure sorts

 

If we nonetheless need to hold working with uncooked indices and use signed sorts, then we will at the very least use “safer” C++ capabilities:

std::ssize() from C++20:

for (int i = ssize(vec) - 1; i >= 0; --i)
    std::cout << i << ": " << vec[i] << 'n';

For the ahead loop, we will write:

for (int i = 0; i < ssize(vec); ++i)
    std::cout << i << ": " << vec[i] << 'n';

Alternatively there are additionally secure comparability capabilities like: cmp_less(), additionally from C++20:

for (int i = 0; std::cmp_less(i, vec.measurement()); ++i)
    std::cout << i << ": " << vec[i] << 'n';

See extra in Integer Conversions and Secure Comparisons in C++20 – C++ Tales.

And right here’s the total instance that you could run @Compiler Explorer

Sidenote: the one warning I get on GCC is conversion for vec[i], this happens once I allow -Wsign-conversion.

Indices are high-quality for vectors and arrays… however how about some generic strategy? See iterators:

2. Iterators

 

Iterators are core elements for algorithms and containers from the Customary Library. They’re very generic and permit us to work with random entry containers, maps, units, or others. For our case with the reverse loop, we will write the next:

#embrace <iostream>
#embrace <set>
#embrace <vector>

void printReverse(auto cont) {
    for (auto it = rbegin(cont); it != rend(cont); ++it)
        std::cout << std::distance(rbegin(cont), it) << ": " << *it << 'n';
}

int predominant() {
    std::vector vec { 1, 2, 3, 4, 5};
    printReverse(vec);

    std::set<std::string> names { "one", "two", "three", "4" };
    printReverse(names);
}

Play at @Compiler Explorer

As you possibly can see, the principle benefit right here is that there’s no have to mess with integral sorts; we will iterate utilizing some summary “proxy” objects.

3. Vary-based for loop with an initializer

 

C++11 helped scale back iterations with iterators, and we will use a range-based for loop to carry out a fundamental iteration. In C++20, we will embrace an “initializer” that may maintain some further properties for the iteration… like a counter:

int predominant() {
    std::vector vec { 1, 2, 3, 4, 5};
    for (int i = 0; const auto& elem : vec)
        std::cout << i++ << ": " << elem << 'n';
}

Play @Compiler Explorer

Beneath, the compiler transforms the code right into a name to start() and finish() for the container in order that we will strive with the reverse and a few further template code:

template<typename T>
class reverse {
non-public:
  T& iterable_;
public:
  express reverse(T& iterable) : iterable_{iterable} {}
  auto start() const { return std::rbegin(iterable_); }
  auto finish() const { return std::rend(iterable_); }
};

int predominant() {
    std::vector vec { 1, 2, 3, 4, 5};
    for (int i = 0; const auto& elem : reverse(vec))
        std::cout << i++ << ": " << elem << 'n';
}

Play @Compiler Explorer.

Be aware that I’ve taken the code concept from Reverse For Loops in C++ @Fluent C++.

You can even take a look at Peter Sommerlad’s repository together with his Reverse Adapter: PeterSommerlad/ReverseAdapter: C++ adapter for traversing a container in a range-for in reverse order (C++17). The adapter is extra generic and helps momentary objects:

utilizing ::adapter::reversed;
for(auto const &i : reversed({0,1,2,3,4,5})) {
    std::cout << i << 'n';

4. Ranges and views

 

Writing customized template code is enjoyable, however for manufacturing, it’s greatest to depend on well-known patterns and strategies. In C++20, we now have ranges that summary the iterators and provides us nicer syntax.

The ranges library provides many “views” that simplify code. For our case, we will use reverse:

#embrace <iostream>
#embrace <vector>
#embrace <ranges>

int predominant() {
    std::vector vec { 1, 2, 3, 4, 5};
    for (int i = 0; const auto& elem : vec | std::views::reverse)
        std::cout << i++ << ": " << elem << 'n';
}

Play @Compiler Explorer.

The code with the pipe operator is equal to:

for (int i = 0; const auto& elem : std::ranges::reverse_view(vec))
    std::cout << i++ << ": " << elem << 'n';

There are tons of different views and extra to return in C++23. Take a look at this record at Ranges library (C++20) – cppreference.com.

5. Algorithms

 

We are able to go even additional and depend on algorithms:

In a fundamental case with the reverse iteration however no indices, we will write:

template <typename T>
void printReverse(const T& cont)  std::views::reverse, 
        std::ostream_iterator<typename T::value_type>( std::cout,"n" ) );

The code above copies the container into the std::cout stream object.

How concerning the full resolution with indices? 🙂

The code for this easy job may be exaggerated, however for an experiment, let’s strive:

#embrace <algorithm>
#embrace <iostream>
#embrace <set>
#embrace <vector>
#embrace <ranges>
#embrace <numeric>

void printReverse(auto cont) {
    std::ranges::for_each(
        std::views::zip(std::ranges::iota_view{0, ssize(cont)}, cont) | std::views::reverse, 
        [](const auto&elem) {
        std::cout << std::get<0>(elem) << ' '
                  << std::get<1>(elem) << 'n';
        }
    );
}

int predominant() {
    std::vector vec { 1, 2, 3, 4, 5};
    printReverse(vec);

    std::set<std::string> names { "one", "two", "three", "4" };
    printReverse(names);
}

Play @Compiler Explorer.

The essential half right here is the zip view accessible in GCC, which is a part of C++23.

Bonus! One reader (thanks Scutum13) posted a cool trick – how about utilizing lambda to hold state throughout iterations?

void printReverse(auto cont) {
    std::ranges::for_each(cont | std::views::reverse, 
        [i=0](const auto& elem) mutable {
            std::cout << i++ << ' ' << elem << 'n';
        }
    );
}

Run at Compiler Explorer

On this case, the code is even easier!

Abstract

 

Taking a loop and remodeling it’s a cool experiment! Due to many strategies accessible in C++20, it’s cool to discover the very best syntax and readability.

Within the article, I confirmed 5 choices, however I may additionally strive coroutine turbines. I guess you even have some cool code snippets!

Again to you

  • Do you write “uncooked” loops or attempt to use algorithms?
  • Is a range-based for loop nonetheless a uncooked loop?
  • Have you ever tried ranges?

Share your suggestions within the feedback beneath.

[ad_2]

Leave a Reply

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