Why doesn't this template function call work inside this function? - c++

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.

Related

Passing a reference array to template pointer array

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.

How to deduce array size out of std::array<T, N>::pointer member/dependent type?

My goal here is to write safe replacement for strcpy for case when destination buffer size is known during compilation, and I would like for buffer size to be deduced, so user won't need to know it. For example:
char xs[2] = { 0 };
strcpy(xs, "abc"); // buffer overflow!
printf("[%s]\n", xs);
Output for this will (hopefully) be:
[abc]
For simple case, when C-style array is passed, it can be written without much fuss:
template<size_t N>
char * safe_strcpy(char (& dst)[N], const char * src) noexcept {
std::snprintf(dst, N, "%s", src);
return & dst[0];
}
Size of array is deduced, snprintf takes care of placing terminating null byte, voilà.
I can sort-of adapt it to std::array as well:
template<size_t N>
typename std::array<char, N>::pointer
safe_strcpy(std::array<char, N> & dst, const char * src) noexcept {
std::snprintf(dst.data(), N, "%s", src);
return dst.data();
}
But this version is not really a drop-in replacement:
std::array<char, 2> ys = {};
strcpy(ys.data(), "abc"); // overflow!
safe_strcpy(ys, "abc"); // ok, but I needed to remove .data()
I want following case to work ok:
safe_strcpy(ys.data(), "abc"); // "a" should appear in buffer
dependent type of ys.data() is std::array<char, 2u>::pointer {aka char*}, so I think it should be possible to deduce array size out of this, but I can't figure out how :/
When I try something like this:
template<size_t N>
typename std::array<char, N>::pointer
safe_strcpy(typename std::array<char, N>::pointer & dst, const char * src) {
// etc...
}
compilation fails with error:
error: no matching function for call to ‘safe_strcpy(std::array<char, 2u>::pointer, const char [4])’
safe_strcpy(ys.data(), "abc");
^
(...)
note: template argument deduction/substitution failed:
note: couldn't deduce template parameter ‘N’
I tried with gcc 5.1.1 and clang 3.5.0, error in both is essentially the same. Is it possible to deduce type out of dependent type in C++ at all?
[edit]
to all you kind people saying, that I should use std::string - you're missing the point here. I could've written same question with any STL container and ::iterator instead of ::pointer.
The data() member of the template<class T> array in namespace std is -according to the C++11 standard- declared as follows
T * data() noexcept;
const T * data() const noexcept;
but not like this:
pointer data() noexcept;
const_pointer data() const noexcept;
Even if it had been declared using the typedef there is no difference. Consider your example code:
std::array<char, 2> ys = {}; // 1
strcpy(ys.data(), "abc"); // 2
safe_strcpy(ys, "abc"); // 3
// 1
Compiler instantiates std::array<char, 2>. Which makes the typedef pointer = char* and compiles (if ever used) a imaginary member pointer data() with the following signature:
char* data();
The typedef is substituted because typedefs and alias names are syntactic sugar for the programmer - not the compiler. The compiler knows that this is char* so there it is.
// 2
You call the template using (as your first argument) a function that has the signature char*(void). (And again std::array<char,2>::pointer is not a type of its own but char*). Therefore, the call is void(char*, char const*) and this is what the compiler is trying to deduce the template from. And this call does not exhibit any information about the array size, nor does it even know about the fact that the pointer comes from an array in the first place.
// 3
Here your call is
void(std::array<char, 2> &, char const *);
and the compiler can deduce size and even character types if required.
The problem you're encountering is that std::array<char, N>::pointer is char* for all N, so when you call ys.data() you pass a char* to the function and there are infiniteN that all match. This the compiler fails it as ambiguous. I took a look at array and I can't see any way to create a drop-in replacement that you're looking for.
But since you're writing C++ you can solve the problem just by using std::string instead of trying to deal with C-strings.
As others said there is no pointer member in std::array that has the information about the size, specially pointer doesn't have that information.
A very non portable alternative is to use ._M_elems instead of .pointer in GNU C++.
int main(){
std::array<double, 10> arr;
assert( std::extent<decltype(arr._M_elems)>() == arr.size() );
}
Should std::array have a standard carray and carray_type member? perhaps yes. Although this variable will decay so easily in a pointer that it won't be very useful.

What does this C++ code mean?

The following code returns the size of a stack-allocated array:
template<typename T, int size>
int siz(T (&) [size])
{
return size;
}
but I can't wrap my head around the syntax.
Especially the T (&) [size] part...
but I can't wrap my head around the syntax. Especially the T (&) [size] part...
That part is a reference to an array. There is the "right-left rule" for deciphering any C and C++
declarations.
Because function templates deduce template argument types from the supplied function arguments what this function template does is deduce the type and element count of an array and return the count.
Functions can't accept array types by value, rather only by pointer or reference. The reference is used to avoid the implicit conversion of an array to the pointer to its first element (aka, array decay):
void foo(int*);
int x[10];
int* p = x; // array decay
foo(x); // array decay again
Array decay destroys the original type of the array and hence the size of it gets lost.
Note, that because it is a function call in C++03 the return value is not a compile time constant (i.e. the return value can't be used as a template argument). In C++11 the function can be marked with constexpr to return a compile time constant:
template<typename T, size_t size>
constexpr size_t siz(T(&)[size]) { return size; }
To get the array element count as a compile time constant in C++03 a slightly different form may be used:
template<class T, size_t size>
char(&siz(T(&)[size]))[size]; // no definition required
int main()
{
int x[10];
cout << sizeof siz(x) << '\n';
double y[sizeof siz(x)]; // use as a compile time constant 10
}
In the above it declares a function template with the same reference-to-an-array argument, but with the return value type of char(&)[size] (this is where the "right-left rule" can be appreciated). Note that the function call never happens at run-time, this is why the definition of function template siz is unnecessary. sizeof siz(x) is basically saying "what would be the size of the return value if siz(x) were called".
The old C/C++ way of getting the element count of an array as a compile time constant is:
#define SIZ(arr) (sizeof(arr) / sizeof(*(arr)))
T (&) [size] is a reference to an array. It needs to be a reference because the following program is not legal:
#include <iostream>
int sz(int *) { std::cout << "wtf?" << std::endl; return 0; }
int sz(int [4]) { std::cout << "4" << std::endl; return 0; }
int main() {
int test[4];
sz(test);
}
This program fails to compile with:
test.cc: In function ‘int sz(int*)’:
test.cc:6:5: error: redefinition of ‘int sz(int*)’
test.cc:3:5: error: ‘int sz(int*)’ previously defined here
because int sz(int [4]) is identical to int sz(int *).
The parenthesis are required to disambiguate here because T& [size] looks like an array of references which is otherwise illegal.
Normally if the parameter wasn't anonymous you would write:
template<typename T, int size>
int sz(T (&arr) [size])
To give the array the name arr. In this instance though all your example code cared about was the deduced size and hence the anonymous argument avoids warnings about unused arguments.
It´s an function which becomes a typename (templates can be used with different typenames) and a size from the outside. It then returns this size.
Stack functions often use size, which is an integer number which shows you the size of the stack-size you request with this function. The & tests just which size of stack T is meant.

array doesn't decay to pointer if passed by const reference in a template function

This question comes from this one:
c++ pass array to function question
but since the OP accepted an answer I guess nobody will read it now.
I tried this code on g++. It seems that the array does not decay to a pointer when passed to this function (the function returns the proper result):
#include <iostream>
template <typename T>
std::size_t size_of_array (T const & array)
{
return sizeof (array) / sizeof (*array);
}
int main ()
{
int a [5];
std::cout << size_of_array (a) << '\n';
}
Another user (sharptooth) said he have the same behavior on VC++ 10 with inlining off.
Can somebody explain? Thanks.
Array decay doesn't just happen -- it only happens when the program would fail to compile without it. When you pass an array by reference, there simply is no need for decay to kick in.
Note that the function template can also be written without dividing ugly sizeof expressions:
template <typename T, std::size_t N>
std::size_t size_of_array(T (&array)[N])
{
return N;
}
When a client calls size_of_array, T and N are automatically deduced by the template machinery.
You haven't written the function to accept a pointer, you've written it to accept a const reference to exactly the type of argement that's passed to it. Pointer decay only happens if you try to assign to a pointer the value of an array.

Why a template function receives 2D array with 1D reference when a normal function doesn't

void fun (char (&a)[2]) // 1D reference
{}
template<typename T, int SIZE>
void funT (T (&a)[SIZE]) // 1D reference
{}
int main ()
{
char c[2][2]; // 2D array
fun(c); // error
funT(c); // ok !!!??
}
I can expect that fun() gives error, but how come funT() works fine!
Is there any reference in the standard for such behavior or Is it a bug in C++ language?
Because the type of c isn't char [2], it doesn't match the first
function. In the template case, T resolves to char [2], which means
that the final argument type is char (&a)[2][2]. (You can think of it
as the T becoming the equivalent of a typedef to char[2], and
expand the argument type based on that.)
T will resolve to char*char[2] and as such there should not be any problems with your templated function.
Edit: Thanks James for pointing that out.