I have this class:
template<typename T>
class array_eraser
{
T* array;
int size_array;
public:
array_eraser(T &a[], int s)
{
size_array = s;
array = new T[s];
array = a;
}
}
I call the class with this line in main:
std::string langs[] = { "C++", "Haskell", "Ada", "Python", "Ada" };
array_eraser<std::string> le(langs, sizeof(langs) / sizeof(langs[0]));
and I got this error: cannot convert argument 1 from 'std::string [5]' to 'T *[]
What did i do wrong?
You are nicely demonstrating why not to use raw arrays.
T &a[] is not a reference to an array of Ts but an array a of references to T. Maybe you wanted T (&a)[] - a reference to an array. But that will not fly either. Because in this case you disable the decay mechanics and you have to specify the size. This can be done using the template trick:
template<std::size_t N>
array_eraser(T (&a)[N]) {}
If you really wanted T& a[], that is a no go, T a[] and T& a[] are never compatible.
If you use array_eraser(T a[], int s), the array is not passed by value, but decays into a pointer. There is no difference between T a[] and T* a. Which brings me to your second mistake. array = a; will not copy arrays but only assign the pointers leaking the newly allocated array. You can copy the array using std::memcpy(array,a,s);.
So, unless you really have to and know what you are doing, use std::array or better std::vector if you need run-time and modifiable length. More typing with std:: stuff is not a good enough reason.
Related
In this MCVE, the compiler complains that processArray can't match the parameter list (arr). The fix is to replace T elements[SIZE] with T (&elements)[SIZE]. Why do I need to do this, and under what circumstance? I wouldn't use & to pass an array into a function ordinarily. (Only reason I thought of it is that's how C++20's new version of istream& operator>> describes its char-array parameter.)
template <typename T, int SIZE>
void processArray(T elements[SIZE])
{
for (int i = 0; i < SIZE; ++i)
elements[i] = 2;
}
int main()
{
int arr[3];
processArray(arr);
return 0;
}
This is because of array decay. Unless you pass an array by reference, it is going to decay into a pointer. That means
void processArray(T elements[SIZE])
is really
void processArray(T* elements)
and there is no way to get what SIZE is for your template since a pointer doesn't know the size of the array it points to.
Once you make the array parameter a reference, you stop this decaying and can get the size out of the array that is passed to the function.
struct S {
double arr[1];
S(double arr[1]) : arr(arr) {}
};
int main(void) {
double arr[1] = {1.2};
S p(arr);
return 0;
}
Hi, this is an extracted problem I encountered in my code.
Do you know why this code won't compile?
main.cpp: In constructor ‘S::S(double*)’:
main.cpp:26:28: error: incompatible types in assignment of ‘double*’ to ‘double [1]’
S(double arr[1]) : arr(arr) {}
I am using g++, compiling and running with
g++ -std=c++17 main.cpp kdtree.h kdtree.cpp && ./a.out
Arrays can't be copied.
int a[3];
int b[3];
a = b; // illegal
Further, when you pass an array to a function, its name decays to a pointer, so S(double arr[1]) is equivalent to S(double* arr). Once you're inside the function, you have to copy the individual elements, so you also need the size of the array:
S(double *x, std::size_t sz) {
std::copy_n(x, sz, arr);
}
You can omit the size if you write the template as a function:
template <std::size_t sz)
S(double (&x)[sz]) {
std::copy_n(x, sz, arr);
}
Or, even better, use std::array, which works the way you expect.
Do you know why this code won't compile?
It won't compile because arrays cannot be copy-initialized (except copy-list-initialized, but you're not doing that).
Arrays have to be copied one element at a time, using a loop. There's also a standard algorithm for copying so you don't need to write that loop yourself: std::copy.
Or, you can use std::array that was introduced in C++11. std::array is copyable.
You can't. What you can do is use the std::array wrapper instead:
struct S {
std::array<double, 1> arr;
S(std::array<double, 1> arr) : arr(arr) {}
};
Although the signature of a constructor is written like that, the array type is adjusted to a pointer, and the array argument decays to a pointer to its first element.
However, the arr member in the struct is still of type array. And a pointer can't be assigned to an array (hence the error).
It's also not possible to assign one array to another (e.g. even if you change it to one of the ways described in the above link), so you need to copy the elements manually with a loop, or using std::copy, or use std::array, as in Ron's answer.
I want to return a pointer to the array I just passed. Why am I not allowed to?
int(*func(int arr[10]))[10]
{
return arr;
}
Because when you pass an array to a function, it decays to a pointer, more precisely to a pointer to the first element.
In your example, the argument arr is actually a pointer and not an array (which is also why you can't use sizeof on the "array", that gets the size of the pointer and not what it points to). When you declare an argument such as int arr[X] (the value of X doesn't matter) it's actually equivalent to declaring int *arr. When you know that it's also very clear what the actual return type should have been: int*:
int* func(int* arr)
{
return arr;
}
One way to remove doubt or ambiguities would have been to use std::array instead:
std::array<int, 10>& func(std::array<int, 10>& arr)
{
return arr;
}
As a side-note, if you hade an actual array of integers, say
int arr[10] = { ... };
then to get a pointer to that (which would be of type int (*)[10]) then you would have to use the address-of operator as usual:
&arr;
Regarding your comment, lets say you had a static array inside the function, and what to return an actual pointer to the array, then you would have to use that "weird" return-type syntax.
int (*func(int x))[10]
{
static int arr[10] = { ... }; // Define and initialize array
// Some other code...
return &arr; // Return a POINTER to the array
}
In the function above, the type of arr is int[10]. A pointer to the array, which is what you get when you use &arr, is of type int(*)[10].
Now expanding this type as a return type to a function we get int (*func(...))[10].
As above, using std::array would have made it clearer (IMO).
The following code doesn't compile, I am trying to figure out how to calculate the size of an array that is passed into a function and can't seem to get the syntax correct.
The error I am getting is :
Error 1 error C2784: 'size_t getSize(T (&)[SIZE])' : could not deduce template argument for 'T (&)[SIZE]' from 'const byte []' 16 1 sizeofarray
Here is the source code:
#include <cstdint>
#include <stdio.h>
template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
return SIZE;
}
typedef std::uint_fast8_t byte;
void processArray(const byte b[])
{
size_t size = getSize(b); // <- line 16 where error occurs
// do some other stuff
}
int main(const int argc, const char* argv[])
{
byte b[] = {1,2,3,4,5,6};
printf("%u\n", getSize(b));
processArray(b);
return 0;
}
If you want this to work, you need to make processArray be a template as well:
template <size_t size>
void processArray(const byte (&b)[size])
{
// do some other stuff
}
C++ does not allow passing arrays by value. If you have a function like this:
void f(int a[5]);
It may look like you are passing an array by value, but the language has a special rule that says a parameter of this form is just another way of saying:
void f(int *a);
So the size of the array is not part of the type at all. This is behavior inhereted from C. Fortunately, C++ has references, and you can pass a reference to an array, like this:
void f(int (&a)[5]);
This way, the size of your array is preserved.
Now, the only remaining trick is to make the function generic, so it can work on any size array.
template <size_t n> void f(int (&a)[n]);
Now, new versions of the function that take references to arrays of different sizes can be generated automatically for you, and the size can be accessed through the template parameter.
As the argument to a function, const byte b[] is treated just like const byte *b. There is no compile-time information about the size of the array that the function was called with.
To pass a reference to the array, you need to make processArray a template and use the same technique. If you don't pass in a reference, the parameter is a pointer, and pointer types don't have array size information.
template<size_t size>
void processArray(const byte (&b)[size]) {
// ...
}
This is the canonical example of why you should use a function template like getSize rather than sizeof, for determining array size.
With sizeof, you'd have gotten the size of a pointer and been none the wiser.
But, this way, you get a compilation error to point out your mistake.
What was the mistake? It was having a function parameter const T arg[] and thinking that this means arg is an array. It's not. This is unfortunate syntax from C that is precisely equivalent to const T* arg. You get a pointer.
This is also why some people think — incorrectly — that "arrays are pointers". They are not. It's just this specific syntax.
First of all, I want to reassure you all that I am asking this question out of curiosity. I mean, don't tell me that if I need this then my design has problems because I don't need this in real code. Hope I convinced you :) Now to the question:
For most types T we can write
T* p = new T;
now what if T is an array type?
int (*p)[3] = new ???; //pointer to array of 3 = new ???
I tried this:
typedef int arr[3];
arr* p = new arr;
but this doesn't work.
Is there any valid syntax for this or it is impossible in C++. If it is impossible, then why? Thanks
Edit: i am guessing I wasn't clear enough. I want to be able to use it in this situation:
void f(int(&)[3]);
int (*p)[3] = new ???;
f(*p);
To get a pointer to an array from new, you have to dynamically allocate a two-dimensional array:
int (*p)[3] = new int[1][3];
The reason you can't do it is that new int[3] already allocates exactly what you want, an object of type int[3]. It's just that what the new-expression returns, is a pointer to its first element. 5.3.4/1:
If the entity is a non-array object,
the new-expression returns a pointer
to the object created. If it is an
array, the new-expression returns a
pointer to the initial element of the
array.
Returning a pointer to the first element is what allows the 3 to be unknown until runtime, so I suppose that by knowing it in advance, you've tripped over flexibility that you aren't using.
I guess the ways around this are to reinterpret_cast back to the pointer type you want (not necessarily portable), or to allocate a struct containing an int[3] (and use a pointer to its data member).
[Edit: er, yeah, or FredOverflow's idea, which has neither disadvantage, but requires use of delete[] instead of delete.]
I guess the moral is, if you write templates that naively allocate some unknown type T with new, then the template won't work when someone passes an array type as T. You'll be assigning it to the wrong pointer type, and if you fix that (perhaps with auto), you'll be deleting it wrongly.
Edit in answer to j_kubik's question:
Here's one way to distinguish between array and non-array types. If you write a function like this, that returns an object that holds the pointer and is capable of correctly deleting it, then you have a generic new/delete for any type T.
#include <iostream>
template <typename T>
void make_thing_helper(T *) {
std::cout << "plain version\n";
}
template <typename T, int N>
void make_thing_helper(T (*)[N]) {
std::cout << "array version\n";
}
template <typename T>
void make_thing() {
make_thing_helper((T*)0);
}
int main() {
typedef int T1;
typedef int T2[3];
make_thing<T1>();
make_thing<T2>();
}
You could always use boost::array, which will be in C++0x.
Otherwise, any solution will be awkward at best: arrays are
broken in C, and C++ maintains compatilibity with C in this
respect. Fred Overflow offered one solution; even easier (but
syntactically noisy) would be to wrap the array in a struct:
struct A { int arr[3]; };
and allocate and manipulate this.
You just do
int *p = new unsigned int [3]
You can then use *p as a pointer or an array i.e. *(p+1) or p[1]