Loop over a specific array of numbers

I have an array: 
const int myArray [] = {4,5,6,7,8,9};

how can I run over the first index of the array until (include) the last index of the array?

for (int i = myArray[0]; i < myArray[lastindex]; i++)
{
}

so I'm asking - how can I get the value of the last index of an array?
Thanks!

Hello,

You get the number of elements in an array with sizeof(array) / sizeof(array[0]).

Supposing an int is 2 bytes, then sizeof(array) will be 12 because there are 6 ints in it, and sizeof(array[0]) will be 2, the size of an int, so 12 / 2 = 6 elements in the array.

So to get the last element in the array you can do array[ ( sizeof(array) / sizeof(array[0]) ) - 1 ] because array index start from 0

1 Like

Thank you! I just find the same answer! was about to edit my post :slight_smile:

You don't need to know the indices to iterate over an array:

const int myArray [] = {4,5,6,7,8,9};
for (int element : myArray) { // for each element in the array
  Serial.println(element);
}

If you do need the number of elements, you can use the sizeof approach, or you could define a len function like this:

// A function that accepts arrays of any type T and any length N, 
// and returns the length N. 
template <class T, size_t N> constexpr size_t len(const T(&)[N]) { return N; }
const int myArray [] = {4,5,6,7,8,9};
Serial.println(len(myArray)); // 6
for (size_t i = 0; i < len(myArray); ++i) {
  Serial.println(myArray[i]);
}
1 Like

This is just great! thanks for sharing!

edit:
so basically this:

const int myArray [] = {4,5,6,7,8,9};
for (int element : myArray) { // for each element in the array
  Serial.println(element);
}

is the same as:

const int myArray [] = {4,5,6,7,8,9};
const int myArraySize = sizeof(myArray)/sizeof(myArray[0]);

for (int i = 0; i < myArraySize;i++) 
{ 
  Serial.println(myArray[i]);
}
1 Like

is it possible to join two arrays elements?

const int myArray1 [] = {4,5,6,7,8,9};
const int myArray2 [] = {10,11,12,13,14,15};
for (int element : (myArray1+myArray2)) { // for each element in the two arrays
  Serial.println(element);
}

so the result that will be printed should be:
4,5,6,7,8,9,10,11,12,13,14,15

Possible?

Indeed.

It is definitely possible, but you need to define yourself how something like that should be iterated over:

Basically:

  • define a struct range_cat_ that contains (pairs of pointers to) all arrays/ranges you wish to iterate over
  • define range_cat_::begin and range_cat_::end methods that return iterators to iterate over this concatenation of ranges
  • define the behavior of these iterators: they increment a pointer to point to the next element in the current array, but if this was the last element in the current array, skip to the first element in the next array
  • define some boilerplate methods required for iterators, such as dereference/element access and comparison to the end iterator
  • define a function to concatenate/combine two instances of range_cat_, resulting in a bigger list of arrays to be iterated over
  • define a function range_cat that takes a list of arrays and returns a range_cat_ instance that iterates over these arrays in sequence
#include <cstddef>

namespace detail {

/// Represents a range that can be iterated over using pointers.
template <class T>
struct subrange { 
    T *begin, *end;
    operator subrange<const T>() const { return {begin, end}; }
};

/// Contains an array of N ranges that can be iterated over.
/// If one range is finished, iteration continues at the start
/// of the next range, until all N ranges have been iterated over.
template <class T, size_t N>
struct range_cat_ {
    subrange<T> subranges[N];
    
    struct iterator {
        subrange<T> *cur_subrange, *end_subrange;
        T *elem_iter;

        /// Advance the iterator.
        iterator &operator++() {
            ++elem_iter;
            if (elem_iter == cur_subrange->end) {
                ++cur_subrange;
                if (cur_subrange != end_subrange)
                    elem_iter = cur_subrange->begin;
            }
            return *this;
        }
        T &operator*() { return *elem_iter; } ///< Element access
        T *operator->() { return elem_iter; } ///< Element access
        /// Compare the iterator to a given end iterator.
        bool operator!=(iterator end) const { return cur_subrange != end.cur_subrange; }
    };

    // These begin/end methods are special, they are called 
    // implicitly when you use a range-based for loop:
    iterator begin() { return { &subranges[0], &subranges[N], subranges[0].begin }; }
    iterator end() { return { &subranges[N], &subranges[N], subranges[N-1].end }; }
};

// The following functions concatenate two range_cat_ instances
// into one bigger range_cat_.

template <class ResT, class T1, size_t N1, class T2, size_t N2>
range_cat_<ResT, N1 + N2> combine_range_cats(const range_cat_<T1, N1> &i1, const range_cat_<T2, N2> &i2) {
    range_cat_<ResT, N1 + N2> res;
    for (size_t i = 0; i < N1; ++i)
        res.subranges[i] = i1.subranges[i];
    for (size_t i = 0; i < N2; ++i) 
        res.subranges[i + N1] = i2.subranges[i];
    return res;
}

template <class T, size_t N1, size_t N2>
range_cat_<T, N1 + N2> operator+(const range_cat_<T, N1> &i1, const range_cat_<T, N2> &i2) {
    return combine_range_cats<T>(i1, i2);
}

template <class T, size_t N1, size_t N2>
range_cat_<const T, N1 + N2> operator+(const range_cat_<const T, N1> &i1, const range_cat_<T, N2> &i2) {
    return combine_range_cats<const T>(i1, i2);
}

template <class T, size_t N1, size_t N2>
range_cat_<const T, N1 + N2> operator+(const range_cat_<T, N1> &i1, const range_cat_<const T, N2> &i2) {
    return combine_range_cats<const T>(i1, i2);
}

} // namespace detail

template <class T, size_t N>
detail::range_cat_<T, 1> range_cat(T (&first)[N]) {
    return detail::range_cat_<T, 1> {{{&first[0], &first[N]}}};
}

template <class T, size_t N, class... Others>
auto range_cat(T (&first)[N], Others&... others) { // return type deduction requires C++14, use traits/metaprogramming in C++11
    return range_cat(first) + range_cat(others...);
}

#include <iostream>

struct {
    template <class T>
    void println(const T &t) { std::cout << t << std::endl; }
} Serial;
int main() {
    const int myArray1[] = {4,5,6,7,8,9};
    int myArray2[]       = {10,11,12,13,14};
    const int myArray3[] = {15,16,17};
    for (int element : range_cat(myArray1, myArray2, myArray3)) {
        Serial.println(element);
    }
}

This requires some knowledge of iterators and how a range-based for loops work under the hood.

I wouldn't recommend this, unless you abstract it away in a library somewhere.

If you want to iterate over multiple arrays with the same loop body, simply extract your loop body as a function and write multiple for loops or calls to std::for_each:

void important_task(int element) {
  Serial.println(element);
}

int main() {
  const int myArray1[] = {4,5,6,7,8,9};
  const int myArray2[] = {10,11,12,13,14,15};
  for (int element : myArray1) 
    important_task(element);
  for (int element : myArray2) 
    important_task(element);
}

If you only need important_task in a single place in your code, or if you need local variables, use a lambda function:

int main() {
  const int myArray1[] = {4,5,6,7,8,9};
  const int myArray2[] = {10,11,12,13,14,15};
  int local_var = 42;
  auto important_task = [&](int element) {
    Serial.println(local_var + element);
  };
  for (int element : myArray1) 
    important_task(element);
  for (int element : myArray2) 
    important_task(element);
}
1 Like

why I cannot do the following:

const int myArray1 [] = {4,5,6,7,8,9};
const int myArray2 [] = {16,15,17,18};
const int myArray3 [] = {41,42,43,44,45,46};
const int myArray4 [] = {51,52,553,54};

const int main_ARRAY = myArray3;

for (int element : main_ARRAY) { 
  Serial.println(element);
}
// another lines of code here //

for (int element : main_ARRAY) { 
  Serial.println(element);
}

so basically instead of changing the array in each for loop inside my function I would like to change it one time up at the const int main_ARRAY = myArray3; without the need to look each time where is the 'for' function.

Because you cannot assign an array to an integer, and you cannot iterate over an integer either.

You could define your own span class that represents a range you can iterate over:

template <class T>
class span {
    private:
        T *begin_, *end_;
    public:
        template <size_t N>
        span(T (&arr)[N]) : begin_(&arr[0]), end_(&arr[N]) {}

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

void setup() {
    Serial.begin(115200);
    const int myArray1[] = {4,5,6,7,8,9};
    const int myArray2[] = {16,15,17,18};
    const int myArray3[] = {41,42,43,44,45,46};
    const int myArray4[] = {51,52,553,54};

    span<const int> main_ARRAY = myArray3;

    for (int element : main_ARRAY) { 
        Serial.println(element);
    }
    
    main_ARRAY = myArray4;
    
    for (int element : main_ARRAY) { 
        Serial.println(element);
    }
}
41
42
43
44
45
46
51
52
553
54
1 Like

It is working just great!
what does Template and span mean?

edit: I need to google all the syntax you offered as it is completely new to me.

This defines a class template with the name span. (A class is a user-defined data type that contains other data members and member functions that can be applied to this data.)
It is a class template and not a class, because you first have to fill in the placeholder T into the template before you get a concrete class. The syntax template <class T> declares this placeholder T that can be replaced by any data type.

Further down in the code, where you write span<const int>, the compiler instantiates an actual class and replaces the template parameter T by const int, implicitly defining the following class (you don't see this, the compiler does this for you):

class span<const int> {
    private:
        const int *begin_, *end_;
    [...]
};

The reason for using a template here is so you can use the span class for any type of arrays, arrays of integers, arrays of booleans, arrays of strings, etc. You make this class independent of the specific type by using templates (generic programming).

Similarly, you use a template argument size_t N in the constructor to support arrays of any length N.

A full explanation of templates and classes is of course beyond the scope of a forum post like this, I'd recommend looking at https://www.learncpp.com/ or https://www.youtube.com/watch?v=LMP_sxOaz6g.

1 Like

How does that work since the span class does not have a custom operator=() function?

There's no span::operator=(T (&)[N]) but you do have a converting constructor span::span(T (&)[N]) and the compiler-generated copy and move assignment operators span::operator=(const span &) and span::operator=(span &&).

The following two lines are equivalent:

main_ARRAY = myArray4;
main_ARRAY = span<const int>(myArray4);

See also: C++ Insights

From that link:

span<const int> main_ARRAY = span<const int>(span<const int>(myArray3));

That's non-intuitive. It looks like it constructs a span object using myArray3 and then constructs another another span object using the first one. Why?

I think the = syntax in the result is a bit confusing because it shows every constructor call explicitly.

The innermost constructor call is the converting constructor from array to span, and the outermost constructor call is the span move constructor. You get a copy constructor call because of the assignment, if you initialize it directly you only get a single constructor call:

span<const int> main_ARRAY {myArray3};
// Insights:
span<const int> main_ARRAY = span<const int>{myArray3};

Note that in the insights pseudocode, the = no longer implies any copy/move constructor or assignment operator calls, it just indicates the assignment to a variable.

These extra move constructors are not actually called, though:
https://en.cppreference.com/w/cpp/language/copy_elision

If you run CppInsights in C++17 mode, you get:

span<const int> main_ARRAY = myArray3;
// Insights:
span<const int> main_ARRAY = span<const int>(myArray3);

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.