Can arrays just be used as iterators in c++? - c++

I'm just looking through std::vector constructors and seeing some people use the constructor that takes 2 iterators and just use it with arrays like:
int arr[5] = [1,2,3,4,5]
std::vector<int> v(arr, arr + arr.size()/arr[0])
Why does this work? So is an array just an iterator as well?
Also one more question here:
int a[5] = {0, 1, 2, 3, 4};
vector<int> v4(a, *(&a + 1));
What exactly is this doing and why is it different from the other thing above?

int arr[5] = [1,2,3,4,5]
std::vector<int> v(arr, arr + arr.size()/arr[0])
Why does this work?
This doesn't work.
So is an array just an iterator as well?
An array is not an iterator. But an array can implicitly convert to a pointer to first element which is an iterator for the array.
int a[5] = {0, 1, 2, 3, 4};
vector<int> v4(a, *(&a + 1));
What exactly is this doing
This is an unnecessarily complex way of writing:
std::vector<int> v4(std::begin(a), std::end(a));

int arr[5];
int *begin=arr;
int *end=arr+sizeof(arr)/sizeof(*arr);
std::vector<int> v(begin, end);
Here arr (as in begin=arr) is a "pointer", which can be used like an iterator. So begin=arr is an "iterator" to the beginning of the array and end is an "iterator" to the "beginning + 5".
To be a little more precise: Here the name of the array "decays" into a pointer. In many circumstances the name can be used as a pointer and would be similar to &arr[0], i.e. a pointer to the first element.
The construction sizeof(array)/sizeof(*array) goes back to the old C days and returns the number of elements in an array. sizeof by itself only returns the size in bytes, but if you divide that by the size of a single element, you get the number of elements.
So now that you have a correct "begin" and "end" iterator, you can construct a std::vector<int> from it. It's just one of the constructors of vector, which takes two iterators.
Your other questions is completely different from the first one.
vector<int> v4(a, *(&a + 1));
Now I am not sure this makes entirely sense. Perhaps you have typo here. This would try to call a vector constructor taking an int[] or int* as the first parameter and an int as the second parameter.
The signature would be std::vector<int>::vector(int*, int), but I do not see such a constructor in the cppreference.

For starters there is a typo
int arr[5] = [1,2,3,4,5]
std::vector<int> v(arr, arr + arr.size()/arr[0])
It seems you mean
int arr[5] = {1,2,3,4,5};
std::vector<int> v(arr, arr + sizeof( arr ) / sizeof( arr[0] ));
Though if your compiler supports the C++ 17 you could write
#include <iterator>
int arr[5] = {1,2,3,4,5};
std::vector<int> v(arr, arr + std::size( arr ));
Arrays used in expressions with rare exceptions are converted to pointers to their first elements.
From the C++ 17 Standard (7.2 Array-to-pointer conversion)
1 An lvalue or rvalue of type “array of N T” or “array of unknown
bound of T” can be converted to a prvalue of type “pointer to T”. The
temporary materialization conversion (7.4) is applied. The result is a
pointer to the first element of the array.
Consider the following demonstration program.
#include <iostream>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
std::cout << "sizeof( arr ) = " << sizeof( arr ) << '\n';
std::cout << "sizeof( arr + 0 ) = " << sizeof( arr + 0 ) << '\n';
}
Its output might loop like
sizeof( arr ) = 20
sizeof( arr + 0 ) = 4
As it is seen from the output the array designator arr was converted to a pointer in the expression arr + 0.
The same way if an array is used as an argument expression and the corresponding parameter type is not a referenced type when the array is converted to a pointer to its first element.
There is no constructor in the class template std::vector that has the first parameter having a referenced type to an array.
But there is a template constructor that accepts two first arguments of the same type
template <class InputIterator>
vector(InputIterator first, InputIterator last, const Allocator& = Allocator());
So in this declaration
std::vector<int> v(arr, arr + sizeof( arr ) / sizeof( arr[0] ));
there is used two above constructor where two first arguments are converted to pointers of the type int *.
Pointers have the same operations as a random access iterator. Thus the vector is created using the range of integer values [arr, arr + sizeof( arr ) / sizeof( arr[0] )
As for this expression
*(&a + 1)
then the expression &a has the type int ( * )[5] that points to the array a.. The expression &a + 1 points to the address pass the last element of the array. The dereferenced expression *(&a + 1) has the type int[5] that denotes an array that again used as an expression is converted to pointer of the type int *.
So you have in fact two expressions of the type int * in this declaration
vector<int> v4(a, *(&a + 1));
However this approach is not correct because you may not dereference a pointer that points after a valid object.

The first piece has several errors:
int arr[5] = [1,2,3,4,5]
std::vector<int> v(arr, arr + arr.size()/arr[0])
The correct version would be:
int arr[5] = {1,2,3,4,5};
std::vector<int> v(arr, arr + sizeof(arr)/sizeof(arr[0]));
where arr plus "the number of elements in arr" is a pointer just past the end of arr.
In the second piece, *(&a + 1), is a pointer. &a is a pointer to a which is an array of 5 integers. Thus adding 1 advances the pointer sizeof(a) bytes (5 times the size of an integer). Hence, again the result is a pointer just past the end of a and thus the vector is initialized with the elements of a.

Related

Getting mismatched types error when i tried to assign numeric value to array object using pointer

I have initiated an array of 6 elements and tried to print it using a function called 'print'. I have used array object from the stl library. I passed the address of the array object to the print function. When I tried to change the value of the array object in the print function I am getting mismatched types error.
#include <bits/stdc++.h>
using namespace std;
void print(array<int, 6> *arr){
for(int i : *arr){
cout<<i<<" ";
}
*(*arr+2)=2;
cout<<endl;
}
int main(){
array<int, 6> arr2={1, 2, 3, 4, 5, 6};
print(&arr2);
print(&arr2);
}
In *(*arr+2)=2; you deference the array pointer and try to add 2 to it and then dereference that result to assign 2. I assume you want to assign 2 to the element at index 2 in the array.
You do not need to use pointers here though, take the array by reference.
And, never #include <bits/stdc++.h>.
#include <array> // include the proper header files
#include <iostream>
void print(std::array<int, 6>& arr) { // by reference
for (int i : arr) {
std::cout << i << ' ';
}
arr[2] = 2; // assign 2 to the element at index 2
std::cout << '\n'; // std::endl flushes the stream which is usually uncessary
}
int main() {
std::array<int, 6> arr2 = {1, 2, 3, 4, 5, 6};
print(arr2); // and don't take the array address here
print(arr2);
}
If you really want to use a pointer here, this could be an option:
#include <array>
#include <iostream>
void print(std::array<int, 6>* arr_ptr) { // pointer
if (arr_ptr == nullptr) return; // check that it's pointing at something
std::array<int, 6>& arr = *arr_ptr; // dereference it
for (int i : arr) {
std::cout << i << ' ';
}
arr[2] = 2;
std::cout << '\n';
}
int main() {
std::array<int, 6> arr2 = {1, 2, 3, 4, 5, 6};
print(&arr2);
print(&arr2);
}
This statement
*(*arr+2)=2;
does not make a sense. For example the operator + is not defined for the class template std::array and as a result this expression *arr+2 is invalid.
You could write
( *arr )[2] = 2;
or for example
*( ( *arr ).begin() + 2 ) = 2;
*(*arr+2)=2;
Would be equivalent to
(*arr)[2] = 2;
if arr were a pointer to an array. I assume that's what you are looking for. But a std::array instance is not an array in that sense. It is an object encapsulating an array, and emulating some of the properties of the underlying array, but it cannot be used interchangeably with the underlying array. In particular, std::array objects do not decay to pointers as actual arrays do, and your code appears to be trying to rely on that.
You could instead do
*((*arr).data() + 2) = 2;
or, more idiomatically,
*(arr->data() + 2) = 2;
to leverage that array-to-pointer decay. Or you could do
arr->data()[2] = 2;
. But none of those is as clear or straightforward as the ...
(*arr)[2] = 2;
... already mentioned, which is what you should do if you must work with a pointer to a std::array instead of a reference to one.
First note that std::array has no operator+. So when you wrote the expression:
*arr+2 // INCORRECT
In the above expression, you are dereferencing the pointer to std::array and then adding 2 to the result. But as i said at the beginning, that there is no operator+ for std::array, this expression is incorrect.
Limitation
Second you program has a limitation which is that the function print can accept a pointer to an std::array with only 6 elements. So if you try to pass a pointer to an std::array of some different size then your program won't work.
Solution
You can fix these by:
Passing the std::array by reference.
Using templates. In particular, using template nontype parameter.
The advantage of using template nontype parameter is that now you can call the function print with an std::array of different size as shown below.
#include <iostream>
#include <array>
template<std::size_t N>
void print(std::array<int, N> &arr){ //by reference
for(const int &i : arr){
std::cout<<i<<" ";
}
arr.at(2) = 2; //use at() member function so that you don't get undefined behavior
std::cout<<std::endl;
}
int main(){
std::array<int, 6> arr2={1, 2, 3, 4, 5, 6};
print(arr2);
std::array<int, 10> arr3 = {1,2,4,3,5,6,7,8,9,10};
print(arr3);
}
Some of the modifications that i made include:
Removed #include <bits/stdc++.h> and only included headers that are needed.
Used templates. In particular, removed the limitation by using template nontype parameter.
Passed the std::array by reference. That is, there is no need to pass a pointer to an std::array. We can just pass the std::array by reference.
Used at() member function so that we don't go out of bounds and so that we don't get undefined behavior.

Generating a ranges::view from a raw pointer (decayed C style array) and a size

Using a function from a C library providing a pointer and a size, is there (or will there be) a way to generate a ranges::view directly from it ?
As I understood, views need a begin iterator and a sentinel, so will I have to copy the content of the decayed C array into a vector to be able to use views in that case ?
You wont need to make any sort of copy. Along with ranges, C++20 will also have std::span which takes a pointer and a size and treats it as a container. You can then pass that to all of the ranges functions. That would look like
std::size_t size;
auto ptr = c_function_call(&size); // or however you get the pointer and size from the c function
std::span container{ptr, size};
// use container with any view you want here
If std::span is not available, you can take advantage of the fact that a pointer is a C++ iterator.
Given a pointer p and a size n, you can use the pair of iterators [p, p + n) to construct a range using ranges::subrange:
void print(int i) { std::cout << i << " "; }
int values[6] = {0, 1, 2, 3, 4, 5};
// Example of a pointer and a size.
const int* ptr = values;
const std::size_t n = 3;
auto r = ranges::subrange(ptr, ptr + n);
ranges::for_each(r, print); // Prints: 0, 1, 2
Working example: https://wandbox.org/permlink/eeIa96ieCb8GIQcM

Range-for-statement cannot build range expression with array function parameter

Why cannot build range expression passing an array as a function argument and using in a range-for-statement.
Thanks for the help
void increment(int v[]){
// No problem
int w[10] = {9,8,7,6,5,4,3,2,1,9};
for(int& x:w){
std::cout<<"range-for-statement: "<<++x<<"\n";
}
// error: cannot build range expression with array function
// parameter 'v' since parameter with array type 'int []' is
// treated as pointer type 'int *'
for(int x:v){
std::cout<<"printing "<<x<<"\n";
}
// No problem
for (int i = 0; i < 10; i++){
int* p = &v[i];
}
}
int main()
{
int v[10] = {9,8,7,6,5,4,3,2,1,9};
increment(v);
}
Despite appearances, v is a pointer not an array - as the error message says. Built-in arrays are weird things, which can't be copied or passed by value, and silently turn into pointers at awkward moments.
There is no way to know the size of the array it points to, so no way to generate a loop to iterate over it. Options include:
use a proper range-style container, like std::array or std::vector
pass the size of the array as an extra argument, and interate with an old-school loop
It's because of the way you pass the array to the function. As written it decays to pointer. Try
template<int N>
void increment(int (&v)[N])
{
for (int x : v) std::cout << "printing " << x << "\n";
}
int main()
{
int v[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 9 };
increment(v);
}
This runs because a reference to an array of N ints is passed in the function and (unlike pointers) range for loops can iterate on those.
The function parameter int v[] is adjasted to int * Pointers do not keep information whether they point a single object or the first object of a sequence of objects.
The range-based for statement in fact uses the same expressions as standard functions std::begin and std::end They cannot be defined for pointers without knowing the size of the array. They can be defined for arrays, not pointers.

Assign array to array

So I am playing around with some arrays, and I cannot figure out why this won't work.
int numbers[5] = {1, 2, 3};
int values[5] = {0, 0, 0, 0, 0};
values = numbers;
The following error appear:
Error 1 error C2106: '=' : left operand must be l-value c:\users\abc\documents\visual studio 2012\projects\consoleapplication7\consoleapplication7\main.cpp 9 1 ConsoleApplication7
Why can't I do like that? What does the error mean?
Arrays have a variety of ugly behavior owing to C++'s backward compatibility with C. One of those behaviors is that arrays are not assignable. Use std::array or std::vector instead.
#include <array>
...
std::array<int,5> numbers = {1,2,3};
std::array<int,5> values = {};
values = numbers;
If, for some reason, you must use arrays, then you will have to copy the elements via a loop, or a function which uses a loop, such as std::copy
#include <algorithm>
...
int numbers[5] = {1, 2, 3};
int values[5] = {};
std::copy(numbers, numbers + 5, values);
As a side note, you may have noticed a difference in the way I initialized the values array, simply providing an empty initializer list. I am relying on a rule from the standard that says that if you provide an initializer list for an aggregate, no matter how partial, all unspecified elements are value initialized. For integer types, value initialization means initialization to zero. So these two are exactly equivalent:
int values[5] = {0, 0, 0, 0, 0};
int values[5] = {};
You can't assign arrays in C++, it's stupid but it's true. You have to copy the array elements one by one. Or you could use a built in function like memcpy or std::copy.
Or you could give up on arrays, and use std::vector instead. They can be assigned.
Array names are constant not modifiable l-value, you can't modify it.
values = numbers;
// ^
// is array name
Read compiler error message: "error C2106: '=' : left operand must be l-value" an l-value is modifiable can be appear at lhs of =.
You can assign array name to a pointer, like:
int* ptr = numbers;
Note: array name is constant but you can modify its content e.g. value[i] = number[i] is an valid expression for 0 <= i < 5.
Why can't I do like that?
Basically this constrain is imposed by language, internally array name uses as base address and by indexing with base address you can access content continue memory allocated for array. So in C/C++ array names with be appear at lhs not a l-value.
values = numbers;
Because numbers and values are pointers.
int numbers[5] = {1, 2, 3};
int values[5] = {0, 0, 0, 0, 0};
for(int i = 0;i < 5; i ++){
values[i] = numbers[i];
}
std::array is a good idea, but this is also possible:
struct arr { int values[5]; };
struct arr a{{1, 2, 3}};
struct arr b{{}};
a = b;
Otherwise, use std::memcpy or std::copy.
I think the reason for non-assignability is that C wants to make sure that any pointers to any element in the array would pretty much always remain valid.
Think about vectors that allow dynamic resizing: the older pointers become invalid (horribly) after resizing happens automatically.
PS. With the design philosophy for vectors, the following block works well.
vector<int> v = {1, 5, 16, 8};
vector<int> v_2 = {7, 5, 16, 8};
v_2 = v;

On Passing a 2D-Array into a function

This is not so much a question on, "How do I pass it into the function?" but rather, "Is this acceptable?"
void func( int **ptr );
int main( int argc, char* argv[] )
{
int arr[][3] = {{1, 2,}, {3, 4}, {5, 6}};
int *pArr = *arr;
(&pArr[0])[1] = 3;
func(&pArr);
cin.get();
return 0;
}
void func( int **ptr )
{
cout << "In func()" << endl;
ptr[0][1] = 5;
}
This works as far as I can tell. It doesn't really feel safe to me, but I like it more than passing a 2D array into a function. Instead, a pointer that does the work for me.
Would this be very confusing for people who had to read my code? Should I use other methods instead? Is it a bad idea to work with pointers to arrays?
Also, question a little bit off-topic. Why can I write:
int arr[] = { 1, 2, 3, 4, 5 };
int *pArr = *arr;
but why can't I write
int arr[][3] = {{1, 2,}, {3, 4}, {5, 6}};
int *pArr = **arr;
or even use a **pArr?
You are getting confused here. int arr[][3] = {{1, 2,}, {3, 4}, {5, 6}}; is not a pointer to arrays of length 3. It's a single solid memory block 9 ints long, which decays into a single int*. The compiler does a 3*first+second mapping for you to this single block of memory.
The reason the subscripting in the function is not valid is because the function has no way of determining what the dimensions of the array are. With a single dimension array, you don't need this information -- you just add the index to the given base pointer. However, with a two dimensional array, you need the dimensions to calculate the final index.
ptr[0][1] = 5; in your function is being translated into *((*(ptr + 0)) + 1) = 5;, which is obviously not what you want given that arr is not an array of pointers.
EDIT: In response to comment:
ptr[0][1] does overwrite the information in cell [0][1] of the array
Yes, you got lucky here -- here's why. When you passed the array into your function, you passed (&pArr[0])[1], which is a pointer to a pointer to the first element of the array. When you did *((*(ptr + 0)) + 1) = 5;, ptr+0 becomes a no-op, leaving *(*ptr + 1) = 5;, which does have well defined behavior given your input data (it's looking at arr as a single-dimension array). If you'd try to change the first dimension of the subscript, however, it would blow up.
Let's dissect this carefully, since array-to-pointer conversions are sometimes confusing.
int arr[][3] = {{1, 2,}, {3, 4}, {5, 6}};
arr is now an array of 3 arrays of 3 ints.
int *pArr = *arr;
*arr uses the array arr in expression, so it decays to a pointer to the first element of arr -- that is pointer to array of 3 ints (the array containing {1,2,0}). Dereferencing that pointer (with *) gives you the array of 3 ints. Now you're using that array in an expression and it decays to a pointer to int, which is assigned to pArr.
(&pArr[0])[1] = 3;
pArr[0] gives the integer at which pArr is pointing (the number 1). &pArr[0] makes a pointer at that integer (which is actually equal to pArr). Indexing that pointer with [1] gives a reference to the next integer after the number 1, which is the number 2. To that reference you're assigning 3. Here's the catch: pointer to an element of an array can only be used to access other elements of the same array. Your pointer points at an element of the array {1, 2, 0}, which you've changed to {1, 3, 0}, and that's fine, but
func(&pArr);
Now you're creating a pointer to pointer to int (since pArr was a pointer to int), and passing that to your function.
ptr[0][1] = 5;
And now you've taken ptr[0], which evaluates to the pointed-to object, which is your original pointer pArr. This line is equivalent to pArr[1] = 5;, and that is still valid (changing your {1,2,0} array to {1,5,0}). However, ptr[1][... would be invalid, because ptr is not pointing at an element of an array of any kind. It's pointing at a standalone pointer. Incrementing ptr will make it point at uninitialized memory and dereferencing that will be undefined behavior.
And for the additional questions:
You should not be able to write this:
int arr[] = { 1, 2, 3, 4, 5 };
int *pArr = *arr;
The array arr decays to a pointer-to-int (pointing at the number 1), dereferencing that gives the integer, and an integer cannot be assigned to the pointer pArr. gcc says error: invalid conversion from 'int' to 'int'*.
Likewise, you cannot write this:
int arr[][3] = {{1, 2,}, {3, 4}, {5, 6}};
int *pArr = **arr;
for the same reason: *arr is the array of 3 ints {1, 2, 0}, **arr is the integer 1, and an integer cannot be assigned to a pointer.
I find multi-dimensional arrays in C++ always a bit confusing and tend to avoid them. There are several solutions that are easier to understand, such as the classes in the Boost.MultiArray or Boost.UBlas libraries, or a vector of vectors, depending on your needs.