Search

6.12a — For-each loops

In lesson 6.3 -- Arrays and loops, we showed examples where we used a for loop to iterate through each element of an array.

For example:

While for loops provide a convenient and flexible way to iterate through an array, they are also easy to mess up and prone to off-by-one errors.

There’s a simpler and safer type of loop called a for-each loop (also called a range-based for-loop) for cases where we want to iterate through every element in an array (or other list-type structure).

For-each loops

The for-each statement has a syntax that looks like this:

for (element_declaration : array)
   statement;

When this statement is encountered, the loop will iterate through each element in array, assigning the value of the current array element to the variable declared in element_declaration. For best results, element_declaration should have the same type as the array elements, otherwise type conversion will occur.

Let’s take a look at a simple example that uses a for-each loop to print all of the elements in an array named fibonacci:

This prints:

0 1 1 2 3 5 8 13 21 34 55 89

Let’s take a closer look at how this works. First, the for loop executes, and variable number is set to the value of the first element, which has value 0. The program executes the statement, which prints 0. Then the for loop executes again, and number is set to the value of the second element, which has value 1. The statement executes again, which prints 1. The for loop continues to iterate through each of the numbers in turn, executing the statement for each one, until there are no elements left in the array to iterate over. At that point, the loop terminates, and the program continues execution (returning 0 to the operating system).

Note that variable number is not an array index. It’s assigned the value of the array element for the current loop iteration.

For each loops and the auto keyword

Because element_declaration should have the same type as the array elements, this is an ideal case in which to use the auto keyword, and let C++ deduce the type of the array elements for us.

Here’s the above example, using auto:

For-each loops and references

In the following for-each example, our element declarations are declared by value:

This means each array element iterated over will be copied into variable element. Copying array elements can be expensive, and most of the time we really just want to refer to the original element. Fortunately, we can use references for this:

In the above example, element will be a reference to the currently iterated array element, avoiding having to make a copy. Also any changes to element will affect the array being iterated over, something not possible if element is a normal variable.

And, of course, it’s a good idea to make your reference const if you’re intending to use it in a read-only fashion:

Rule

In for-each loops element declarations, if your elements are non-fundamental types, use references or const references for performance reasons.

Rewriting the max scores example using a for-each loop

Here’s the example at the top of the lesson rewritten using a for each loop:

Note that in this example, we no longer have to manually subscript the array or get its size. We can access the array element directly through variable score. The array has to have size information. An array that decayed to a pointer cannot be used in a for-each loop.

For-each loops and non-arrays

For-each loops don’t only work with fixed arrays, they work with many kinds of list-like structures, such as vectors (e.g. std::vector), linked lists, trees, and maps. We haven’t covered any of these yet, so don’t worry if you don’t know what these are. Just remember that for each loops provide a flexible and generic way to iterate through more than just arrays.

For-each doesn’t work with pointers to an array

In order to iterate through the array, for-each needs to know how big the array is, which means knowing the array size. Because arrays that have decayed into a pointer do not know their size, for-each loops will not work with them!

Similarly, dynamic arrays won’t work with for-each loops for the same reason.

Can I get the index of the current element?

For-each loops do not provide a direct way to get the array index of the current element. This is because many of the structures that for-each loops can be used with (such as linked lists) are not directly indexable!

Since C++20, range-based for-loops can be used with an init-statement just like the init-statement in if-statements. We can use the init-statement to create a manual index counter without polluting the function in which the for-loop is placed.

The init-statement is placed right before the loop variable:

for (init-statement; element_declaration : array)
   statement;

In the following code, we have two arrays which are correlated by index. For example, the student with the name at names[3] has a score of scores[3]. Whenever a student with a new high score is found, we print their name and difference in points to the previous high score.

Output

Alex beat the previous best score of 0 by 84 points!
Betty beat the previous best score of 84 by 8 points!
The best score was 92

The int i{ 0 }; is the init-statement, it only gets executed once when the loop starts. At the end of each iteration, we increment i, similar to a normal for-loop. However, if we were to use continue inside the loop, the ++i would get skipped, leading to unexpected results. If you use continue, you need to make sure that i gets incremented before the continue is encountered.

Before C++20, the index variable i had to be declared outside of the loop, which could lead to name conflicts when we wanted to define another variable named i later in the function.

Conclusion

For-each loops provide a superior syntax for iterating through an array when we need to access all of the array elements in forwards sequential order. It should be preferred over the standard for loop in the cases where it can be used. To prevent making copies of each element, the element declaration should ideally be a reference.

Quiz time

This one should be easy.

Question #1


Declare a fixed array with the following names: Alex, Betty, Caroline, Dave, Emily, Fred, Greg, and Holly. Ask the user to enter a name. Use a for each loop to see if the name the user entered is in the array.

Sample output:

Enter a name: Betty
Betty was found.
Enter a name: Megatron
Megatron was not found.

Hint: Use std::string_view as your array type.

Show Solution


6.13 -- Void pointers
Index
6.12 -- Member selection with pointers and references

318 comments to 6.12a — For-each loops

  • Lordi

    Quiz!
    not so perfect I know, so many neat solutions in the comments section :(

    • nascardriver

      That doesn't work. You're only ever checking the first element in `names`, because you `break` in the first iteration.

    • Tony

      You can "fix" your program by replacing:

      with

      ...and removing "break" from the else statement. This was my first attempt, but it's the worst solution.

      As far as I can tell (I just understood it after a couple of chapters...) using "return" is not maintanable, since if we wanted to add new code later the program would simply shut down when the if condition evaluates to true. Meanwhile, your program actually "breaks" if "input" is not "name" (so if your input isn't "Alex", the loop will terminate and thus "Name was not found" will be printed). Using the bool is simply the best way here!

  • Lordi

    "Before C++20, the index variable i had to be declared outside of the loop, which could lead to name conflicts when we wanted to define another variable named i later in the function."
    so can't I use the last example with different way? I tried to create the variable outside the loop in many ways but for some reason there is error in the variable (score)!
    this line was of many that failed

  • Innervate

    When we use the ranged based for loop, why are we not able to initialize the new variable?

    such as:
    for (element_declaration { 0 } : array)
       statement;

    is it possible to skip any iterations with the range based loop (for example if I wanted to start from the second element on wards)?
    or is it best to use normal for loops for that instance?

    Thanks!

    • nascardriver

      The element gets initialized with the array elements, it doesn't make sense to provide an initializer.

      A range-based for-loop can't skip elements (Other than using `continue`). You can skip elements with `std::for_each` (Covered later in this chapter).

  • Sonia

    I got lots of errors from the following code! Would you please help me?

    • nascardriver

      Please post the full code and error messages.

      • Sonia

        ERROR MESSAGES:
        1) 'begin': no matching overloaded function found    
        2) Failed to specialize function template 'unknown-type std::begin(_Container &)'
            
        3) Severity    Code    Description    Project    File    Line    Suppression State
        4)Error    C2784    'const _Elem *std::begin(std::initializer_list<_Elem>) noexcept': could not deduce template argument for 'std::initializer_list<_Elem>' from 'std::string_view []'            
        no matching overloaded function found
        5)Severity    Code    Description    Project    File    Line    Suppression State
        Error    C2893    Failed to specialize function template 'unknown-type std::end(_Container &)'

        6)
        Severity    Code    Description    Project    File    Line    Suppression State
        Error    C2679    binary '==': no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion)

        • nascardriver

          `main::names` decays to a pointer when you pass it to `searchName`. You cannot use a range-based for-loop on a pointer. Pass the array's length to `searchName` as a separate parameter and use a regular for-loop. We'll show better alternatives later in this chapter.

          • Sonia

            Thank you so so much.

            I found the below snippet in the comments, what is this format "std::string_view (&nms)[8]"?

    • Amir

      btw you can't return std::string_view from function unless you made std::string name in ur function statick because once function stops std::string_view has nothing to show. maybe nascardriver has another opinion . thanks for sharing :)

  • Sonia

    Although this code won’t compile, but there is another error regarding conversion from const argument to non-const parameter.

  • yup

    Hi! When I try to use std::string_view, my compiler gives me an error.

    (Obviously, this isn't my finished answer to the quiz question.)

    It says, "namespace 'std' has no member 'string_view'". Do you have any idea why this is happening? TIA

  • choofe

    Hi.
    Is this true:
    If the iterator is not a reference then any changes to iterator element will be lost and no change will be made to the container?

    • nascardriver

      It can be true depending on the iterator. It's mostly wrong, because iterators don't store elements directly, they store pointers to the elements, so copying the iterator doesn't matter. The pointer stays the same and still points into the real container.

      • choofe

        I think I hit the part "It can be true depending on the iterator"
        I have container of a class object that each
        object may have some container of fundamental data type as private member.
        In this case is my conclusion true:
        every time the base iterator gets a pointer to each member of container, the class generates a copy (an unknown class object is created through the corresponding structor) and assign its pointer to iterator.Every statements then will be executed in loop just fine.changes are made here! but once the current loop is ended the unknown object is destroyed too and thus no change will be made to the container!
        If this is true how can one save the changes to original container(rather than using auto&)!

        • nascardriver

          Please post a minimal code example of your situation that compiles. I can't follow your description.

          • choofe

            Hi. This is what I'm talking about:

            result:
            1
            5
            1
            5
            1
            5
            1
            5

            1
            1
            1
            1

            In first for(auto i) if we change to for(auto& i) the problem is solved. and this will be result:
            1
            5
            1
            5
            1
            5
            1
            5

            1
            5
            1
            5
            1
            5
            1
            5

            • nascardriver

              Alright, with range-based for-loops, you don't get to see the iterators, you're accessing the elements directly, so it doesn't matter how the iterator is implemented. I thought you were calling `begin` and `end` manually and passing around the iterators.
              Your `auto` resolves to `int` and `Something`, no iterators here. If you don't use references, you're copying the elements. This is ok in `print`'s loop, but extremely wasteful in `main`'s loop. Use `const auto&` instead, or `auto&` if you want to modify the elements.
              If you want to modify the elements, you have to use references. Why don't you want to do this?

              • choofe

                No particular reason!
                Just wondering. I had forgotten this rule of auto& and it confuses me to headache. And honestly I gave up and switched to classic loop
                This is silly but happened to me. Rookie mistake.

  • Eric

    Hi!
    I broke the program into functions but in passing a reference to the array to a function, what's a good way to also pass the length information? Or in general is there a better way to pass arrays to functions?

    • nascardriver

      When you pass an array by reference, you have to specify its size. C-style arrays aren't used much anymore. You'll learn about `std::array` and `std::vector` later in this chapter.

  • bobby

    you guys should talk about #include <array>

    • nascardriver

      `std::array` is introduced later in this chapter.

      - Avoid `using namespace`, it will lead to name collisions.
      - Use `std::string_view` for strings that are known at compile-time.
      - Initialize variables with list initialization for higher type-safety.
      - Use ++prefix unless you need postfix++.
      - There's absolutely no reason to use a `switch`-statement for booleans.
      - Don't use `std::endl` unless you have a reason to.

      I have a strong feeling you came here from another language or another tutorial and skipped a lot of lessons up to this point. If you're serious about learning C++, I suggest taking a few steps back and reading through the lessons, or at least make sure you understood all examples and quizzes up to now.

  • Sinethemba

    Hi Alex/nascardriver. In the section "For-each loops and non-arrays", isn't the parameter type <int> for the vector missing in the below line code?

    Please also see my solution to the quiz question below. Feel free to comment.

    • nascardriver

      > isn't the parameter type for the vector missing
      It has been optional since C++17 if the type can be deduced from the elements. The numbers we're initializing the vector with are `int`, to the vector's element type is `int`.

      If the body of something exceeds 1 line, I recommend wrapping it in curly braces.
      Using `==` for comparisons is more intuitive than using type-specific functions.

  • koe

    Here you say "In the above example, element will be a reference to the currently iterated array element, avoiding having to make a copy.", while in 6.11 you point out that references can't be reassigned to a different variable once created. What is going on with for-each loops to avoid that restraint?

  • Ian

    Hi!
    I create a function inNameList() to do the for-each loop.
    Is there any problem, or should I avoid passing array to a function?
    Thanks!

    • nascardriver

      Now `inNameList` only works with arrays of 8 elements. If you're cool with that, that's fine. We'll cover better methods later.

      • Ian

        Hi!
        The better methods you mentioned is std::array?

      • Bumblebee

        I would have thought, in Ian's work, the use of &names parameter in `inNameList` is sufficient efficient as &names already avoids the copying of the array.  Adding string_view would not add further advantage.  Your comments please.

        • nascardriver

          The reference isn't used for performance in this case, but to preserve the size of the array. This allows @Ian to use a range-based for-loop.

          The `std::string_view` prevents run-time allocation of the names and allows us to make the array `constexpr`.

      • sn0ot

        I had a similar approach. Is there a way to type cast a whole array? NOTE: this won't compile until types match.

        • nascardriver

          No, you have to create another array and copy the data into it.

          Booleans are false/true, not 0/1

  • JasonA

    Why does question #1 include a hint to use std::string_view for the array ? What is the reason that an array of std::string won’t work ?
    It’s nice to have a hint, but even better to understand *WHY* the hint is provided, and why it won’t work without it.

    • nascardriver

      `constexpr` should be used as much as possible. It speeds up the application and prevents undefined behavior. `std::string` cannot be `constexpr` (As of C++17, changed in C++20), because it allocates the string dynamically, which requires the program to be running. `std::string_view` doesn't need to allocate a new string, it only views the existing string literal.

  • Gabe

    Quiz

  • Constantine

    So if we use string_view for the array, why do we need reference to const in this for loop:

    ?
    I thought, the string_view is cheap enough not to use references.

  • mesut

    Hi. Why you used constexpr instead const and why you use static_cast?

    • nascardriver

      `constexpr` should be used whenever possible. It allows this variable to be used in constant expressions and can provide higher safety, because it doesn't allow undefined behavior.
      `std::size` returns a `std::size_t`, which is an unsigned integer type. With a high warning level, the implicit conversion from unsigned to signed will cause a warning.

  • Dudz

    This is my poor answer to the quiz. I would like to use for-each loop in another function to find a name but i cannot make it work.

    • nascardriver

      > I would like to use for-each loop in another function
      You can't do that with a decayed array. We cover standard containers later, then you can do it.

      - Initialize empty strings with empty curly braces. That's faster.
      - The only string difference in line 18 is "not ". The rest can be printed unconditionally.
      - `names` is compile-time constant, it can be `constexpr`.

  • Mert

    Hello nascardriver and Alex,

    #include <iostream>
    #include <vector>

    int main()
    {
        std::vector fibonacci{ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; // note use of std::vector here rather than a fixed array

        for (const auto &number : fibonacci)
            std::cout << number << ' ';

        std::cout << '\n';

        return 0;
    }

    In this code why did whe use "const auto &number" for for-each loop what does it means because, I also saw this in the summary section but didn't get the idea why we use this instead of "auto number : fibonacci" is it related to length of the dynamic array could you give me some insight about it? Have good day/night.

    • nascardriver

      That example shouldn't use a reference. I update the lesson. References are used to prevent copies of large types. Since `fibonacci` is a vector of `int`, and `int` is a type that's fast to copy, no reference should be used.

  • Just thought I'd share my implementation of the quiz. I used a vector instead.

    • nascardriver

      Congratulations on solving this quiz!
      Global variables are evil, and unnecessary in this example. `names` can be moved into `checkIfNameIsFoundInListAndNotifyUser`.

  • Vova

    Hi,
    I just noticed a rule suggested in the "For each loops and references" section:
    Rule: In for-each loops element declarations, if your elements are non-fundamental types, use references or const references for performance reasons.

    But in the examples above didn't we use int? Isn't int a fundamental data type?

    • nascardriver

      You're right! `int` is a fundamental type and should be copied rather than referenced. I updated the example to use `std::string`. Thanks!

  • Siva

    Hi,

    Can you please clarify below point.

    How does for each loop know size of linked list in advance?

    Regards,
    Siva

    • nascardriver

      Hi Siva!

      The range-based loop doesn't know the length. It keeps looping until it finds the end of the list.

      Where `list.first` is the first element, `list.end` is the end marker, and `element.next` is the next element in the list.

      A lesson about this is in the making. Look up "iterators" for more information.

  • Luiz Carlos

    I have a question regarding the 'nameInput' variable I defined above the infinite loop. Where is the best place for it to be defined? Inside or outside the loop?

    Also, is this an efficient way of accomplishing what I did?

    • nascardriver

      Outside the loop is probably faster, because the string can re-use memory.
      Generally, inside the loop is better to limit the scope.

      Efficiency really isn't a concern when you're blocking to ask for user input, as that's what takes up the majority of time.
      A minor performance improvement is to not use `"text" << '\n'` but instead `"text\n"`. If you don't like that, you can also do `"text" "\n"`. This will concatenate the strings at compile-time but keep a space between the text and the line break.

  • alfonso

    Pretty much the same solution.

    • Hello alfonso!

      Unless you have a specific reason to manually set the array's length (Line 24), you should n't do so. If you create the array first and then get its length, if at all, your code is easier to update. In this case, you can remove `len` and your program will work just fine.
      Your code looks good otherwise, keep it up :)

  • Alex

    It might be a good idea to change the title of this article; foreach loops are more commonly called "range-based for loops" in C++. "Foreach" is more of a C# term.

  • pibaereg

    Hi!
    Is it okay to simply add a return statement like this? Or should I avoid doing this? Thanks!

    • It's best to avoid `return` and `break`. If you think you need to `return` from a loop, move your code into a separate function, eg. `arrayContains`. Then call `arrayContains` from `main`. The `return` in `arrayContains` is easily spotted, not so much the `return` in `main`.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]