Range-based for loop on a dynamic array?
Asked Answered
B

6

31

There is a range-based for loop with the syntax:

for(auto& i : array)

It works with constant arrays but not with pointer based dynamic ones, like

int *array = new int[size];
for(auto& i : array)
   cout<< i << endl;

It gives errors and warnings about failure of substitution, for instance:

Error] C:\Users\Siegfred\Documents\C-Free\Temp\Untitled2.cpp:16:16: error: no matching function for call to 'begin(int*&)'

How do I use this new syntax with dynamic arrays?

Bugeye answered 9/4, 2013 at 14:37 Comment(8)
what does the errors say? At least post one errorCracknel
it's called range-based for loop and SO and Google have tons of examplesGrader
[Error] C:\Users\Siegfred\Documents\C-Free\Temp\Untitled2.cpp:16:16: error: no matching function for call to 'begin(int*&)'Bugeye
Second instance is a typo. Should be for (auto& i: arr) not array.Michey
Hmm when I read "new C++ for loop on a dynamic array" I thought "what a silly question: it just works!". Then I saw the question and realized "oh, the poster did not mean vector when they typed 'dynamic array'..."Biserrate
I took the liberty to modify your question a bit. If you feel that it is incorrectly modified there is an edit button under the question in order to fix it (or just write @Default to ping me)Cracknel
@R.MartinhoFernandes is there a better definition then "dynamic array" describing what the OP refers to?Cracknel
@Default Maybe a "pointer to a dynamically allocated array" (a bit cumbersome, but would have made the OP's error of thinking about a pointer as an array more obvious).Sandell
B
27

To make use of the range-based for-loop you have to provide either begin() and end() member functions or overload the non-member begin() and end() functions. In the latter case, you can wrap your range in a std::pair and overload begin() and end() for those:

    namespace std {
        template <typename T> T* begin(std::pair<T*, T*> const& p)
        { return p.first; }
        template <typename T> T* end(std::pair<T*, T*> const& p)
        { return p.second; }
    }

Now you can use the for-loop like this:

    for (auto&& i : std::make_pair(array, array + size))
        cout << i << endl;

Note, that the non-member begin() and end() functions have to be overloaded in the std namespace here, because pair also resides in namespace std. If you don't feel like tampering with the standard namespace, you can simply create your own tiny pair class and overload begin() and end() in your namespace.

Or, create a thin wrapper around your dynamically allocated array and provide begin() and end() member functions:

    template <typename T>
    struct wrapped_array {
        wrapped_array(T* first, T* last) : begin_ {first}, end_ {last} {}
        wrapped_array(T* first, std::ptrdiff_t size)
            : wrapped_array {first, first + size} {}

        T*  begin() const noexcept { return begin_; }
        T*  end() const noexcept { return end_; }

        T* begin_;
        T* end_;
    };

    template <typename T>
    wrapped_array<T> wrap_array(T* first, std::ptrdiff_t size) noexcept
    { return {first, size}; }

And your call site looks like this:

    for (auto&& i : wrap_array(array, size))
         std::cout << i << std::endl;

Example

Both answered 10/4, 2013 at 7:42 Comment(4)
Is there no built-in construct for this? Anyone who has a dynamic C-style array needs to write their own wrapped_array struct?Spiritualize
@Spiritualize Not yet, but there will be in C++20. I present that in my new answer below.Banda
"It is undefined behavior to add declarations or definitions to namespace std or to any namespace nested within std, with a few exceptions noted below" en.cppreference.com/w/cpp/language/extending_stdPlanometer
Gross. . . . . .Oculomotor
A
19

You can't use range-for-loop with dynamically allocated arrays, since compiler can't deduce begin and end of this array. You should always use containers instead of it, for example std::vector.

std::vector<int> v(size);
for(const auto& elem: v)
    // do something
Arvy answered 9/4, 2013 at 14:40 Comment(6)
well, it worked! i never knew C++ would be this type-safe, i came from C, so a got used to manipulating dynamic and constant arrays in the same way.Bugeye
@MauriceRodriguez Well, C has this difference between arrays and pointers in exactly the same way. For example sizeof(array) returns completely different things in C, too, depending on if it's int *array = malloc(N*sizeof(int)); or int array[N];. So it is just because C made it easier for you to incorrectly ignore this difference, not that this difference wasn't there.Sandell
I disagree that you should always use a std::vector over a dynamically allocated array. =) There are cases where it's important to use the dynamic version. Also, since we're using C++11 anyway, std::array is a good idea to consider when you're using a fixed reasonably sized array.Dyke
Could you give me an example, please? Also, std::array is good, of course, but you must know it's size at compile time. This may not cover the OP case. As replacement for std::vector I can suggest std::dynarray(since C++1y) if you don't need to increase size an array's size.Arvy
@soon dynarray was voted out of C++14 ("1y") and still only has experimental status at best, so it should probably not be used in production.Fourteen
1. Other answers show how range based loop can be done over dynamically allocated array if its size is known, so "you can't do it" is an overstatement. 2. std::vector/std::array can't always be used (e.g. imagine having to deal with a legacy interface).Planometer
S
12

You can't perform a range based loop directly over a dynamically allocated array because all you have is a pointer to the first element. There is no information concerning its size that the compiler can use to perform the loop. The idiomatic C++ solution would be to replace the dynamically allocated array by an std::vector:

std::vector<int> arr(size);
for(const auto& i : arr)
  std::cout<< i << std::endl;

Alternatively, you could use a range type that provides a begin and end iterator based on a pointer and an offset. Have a look at some of the types in the boost.range library, or at the GSL span proposal (example implementation here, reference for C++20 proposed type here).


Note that a range based for loop does work for std::array objects of fixes size plain arrays:

std::array<int,10> arr;
for(const auto& i : arr)
  std::cout<< i << std::endl;

int arr[10] = .... ;
for(const auto& i : arr)
  std::cout<< i << std::endl;

but in both cases the size needs to be a compile-time constant.

Snowfield answered 9/4, 2013 at 14:48 Comment(4)
You can already range-for over a bare automatically allocated array, no need to add the wrapper class just for this. int a[]{1, 2, 3}; for (auto it: a) std::cout << it << ' '; Each count of elements creates a distinct type for which end can be deduced when compiling the loop.Fourteen
1. Other answers show how range based loop can be done over dynamically allocated array if its size is known, so "you can't do it" is an overstatement. 2. std::vector/std::array can't always be used (e.g. imagine having to deal with a legacy interface).Planometer
@DevNull Sure, thanks. I edited the answer to clarify what I meant.Snowfield
@DevNull And a section on alternatives when all you have is a pointer and a size.Snowfield
B
8

C++20 adds std::span, which allows looping like this:

#include <iostream>
#include <span>

int main () {
    auto p = new int[5];
    for (auto &v : std::span(p, 5)) {
        v = 1;
    }
    for (auto v : std::span(p, 5)) {
        std::cout << v << '\n';
    }
    delete[] p;
}

This is supported by current compilers, e.g. gcc 10.1 and clang 7.0.0 and later. (Live)

Of course, if you have the choice, it is preferable to use std::vector over C-style arrays from the get-go.

Banda answered 22/5, 2018 at 16:12 Comment(0)
P
2

Instead of defining std::begin and std::end for std::pair of pointers (defining them in std::, by the way, is undefined behaviour) and rolling out your own wrapper, as suggested before, you can use boost::make_iterator_range:

size_t size = 16;
int *dynamic_array = new int[size];
for (const auto& i : boost::make_iterator_range(dynamic_array, dynamic_array + size))
    std::cout << i << std::endl;

Live example.

Planometer answered 25/7, 2018 at 0:31 Comment(0)
R
1

From C++20 views, we can use subrange also (std::ranges::subrange)

auto p = new int[5];

//INPUT DATA TO TEST
int i = 0;
for (auto& v : std::ranges::subrange(p, p + 5)) {
    v = ++i;
}

//USE SUBRANGE
for (auto v : std::ranges::subrange(p, p + 5))
    std::cout << v << '\n';
Rhinoscopy answered 24/7, 2022 at 3:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.