On Passing a 2D-Array into a function - c++

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.

Related

C++ Array Reinitialization using Braces [duplicate]

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;

Sorting 2d array using std sort without vector [duplicate]

I cannot seem to sort a 2 dimensional c array with std::sort. I can however sort a one dimensional array. This is in a case where I am being handed a c array in a c++ program and am hoping to sort without copying it over to a std::array. Maybe there is some way to turn it into a std::array without copying it? That sounds doubtful to me as any std::array would then call a destructor on memory that it does not own.
Sorting One dimensional c style array works just fine:
int len = 5;
auto one_dim_less = [](int a, int b){
return a < b;
};
int one_dim[] = {4, 0, 3, 1, 2};
std::sort(one_dim, one_dim + len, one_dim_less);
Attempting to sort two dimensional c style array by second number does not compile:
int len = 5;
auto two_dim_less = [](int a[2], int b[2]){
return a[1] < b[1];
};
int two_dim[][2] = {{1,8}, {2,4}, {3,10}, {4,40}, {5,1}};
std::sort(two_dim, two_dim + len, two_dim_less);
Maybe there is some way to turn it into a std::array without copying
it?
Perhaps not turning into a std::array per se, but an alternative approach might be to cast the 2D C-style arrays into a std::array reference just for the sorting. Doing so in reliance on the standard saying an std::array representation in memory at least begins with its C-style array equivalent. See here under [array.overview§2]:
An array is an aggregate that can be list-initialized with up to N
elements whose types are convertible to T.
In practice, the following usage of reinterpret_cast is most probably safe, but do note that unless there is a special exception for it somewhere in the standard, it would formally be undefined behaviour:
#include <algorithm>
#include <array>
#include <iostream>
int main() {
auto two_dim_less = [](std::array<int, 2>& a, std::array<int, 2>& b) {
return a[1] < b[1]; };
int two_dim[][2] = {{1, 8}, {2, 4}, {3, 10}, {4, 40}, {5, 1}};
std::array<std::array<int, 2>, 5>& arr =
*reinterpret_cast<std::array<std::array<int, 2>, 5>*>(&two_dim);
std::sort(arr.begin(), arr.end(), two_dim_less);
for (int i = 0; i < 5; i++)
std::cout << two_dim[i][0] << ", " << two_dim[i][1] << '\n';
return 0;
}
Output:
5, 1
2, 4
1, 8
3, 10
4, 40
Regarding the use of std::qsort(), note that it is potentially slower than std::sort() due to the latter allowing to inline the comparisons while the former doesn't.
std::sort() requires the objects it's used to sort to be MoveAssginable.
Arrays are not MoveAssginable (nor assignable at all).
Try using an array of structures or std::pairs instead.

Initialise a dynamically allocated array [duplicate]

In C++, I can statically initialize an array, e.g.:
int a[] = { 1, 2, 3 };
Is there an easy way to initialize a dynamically-allocated array to a set of immediate values?
int *p = new int[3];
p = { 1, 2, 3 }; // syntax error
...or do I absolutely have to copy these values manually?
You can in C++0x:
int* p = new int[3] { 1, 2, 3 };
...
delete[] p;
But I like vectors better:
std::vector<int> v { 1, 2, 3 };
If you don't have a C++0x compiler, boost can help you:
#include <boost/assign/list_of.hpp>
using boost::assign::list_of;
vector<int> v = list_of(1)(2)(3);
You have to assign each element of the dynamic array explicitly (e.g. in a for or while loop)
However the syntax int *p = new int [3](); does initialize all elements to 0 (value initialization $8.5/5)
To avoid endless push_backs, I usually initialize a tr1::array and create a std::vector (or any other container std container) out of the result;
const std::tr1::array<T, 6> values = {T(1), T(2), T(3), T(4), T(5), T(6)};
std::vector <T> vec(values.begin(), values.end());
The only annoyance here is that you have to provide the number of values explicitly.
This can of course be done without using a tr1::array aswell;
const T values[] = {T(1), T(2), T(3), T(4), T(5), T(6)};
std::vector <T> vec(&values[0], &values[sizeof(values)/sizeof(values[0])]);
Althrough you dont have to provide the number of elements explicitly, I prefer the first version.
No, you cannot initialize a dynamically created array in the same way.
Most of the time you'll find yourself using dynamic allocation in situations where static initialization doesn't really make sense anyway. Such as when you have arrays containing thousands of items. So this isn't usually a big deal.
Using helper variable:
const int p_data[] = {1, 2, 3};
int* p = (int*)memcpy(new int[3], p_data, sizeof(p_data));
or, one line
int p_data[] = {1, 2, 3}, *p = (int*)memcpy(new int[3], p_data, sizeof(p_data));
Never heard of such thing possible, that would be nice to have.
Keep in mind that by initializing the array in the code that way
int a[] = { 1, 2, 3 };
..... only gains you easier code writing and NOT performance.
After all, the CPU will do the work of assigning values to the array, either way you do it.

What happens when creating an array of a larger size than initialized C++

In c++, if you do something like:
int array [2][4] =
{
{1,2,3},
{4,5,6,7}
}
Is array [0][3] null or nonexistent?
It is actually value initialized, so it will be 0. It is similar to if you had
int values[5] = {1,2};
This would produce the array
{1, 2, 0, 0, 0}
Here is a thorough description of zero-, default-, and value-initialization if you're interested in the definitions and when each applies.

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;