Const char array with template argument size versus char pointer

Today I saw a construction of the following type:

template<unsigned int N> unsigned int f(const char (&a)[N]); 

Is there any reasonable point that this one has:

 unsigned int f(const char *a); 

I vaguely understand that link exchange is associated with the latter, but is it really so bad that it needs to be replaced with obscure code twice as much?

(Unfortunately, I cannot ask the author of the code, otherwise I would)

+5
source share
6 answers

The purpose of passing any raw function pointer is that the caller has some idea about:

  • What he points to.
  • How much does this indicate.

C-style strings have the latter assumption as input parameters, since it is assumed that "how much" is considered to be a char in the null terminator.

But what if you don't pass a C style string? What if it's just a sequence of values ​​of zero or more char ? Well, if so, then:

 void f(const char *s) { // how many char does s refer to? } 

The logical conclusion would be to do this:

 void f(const char *s, std::size_t N) { // Now the caller is telling us there are N chars at s } 

and this is not uncommon, although a potential point of error, if the caller gives us the wrong length (never say never).

But what if there was a way to destroy this data from the actual type of the variable passed to the function using a deduction through a template parameter without a type? What if the caller refers to a fixed array?

 template<std::size_t N> void f(const char(&ar)[N]) { // we know the caller is passing a const-reference to a // char array declared of size N. The value N can be used // in this function. } 

Now we know both points on our list: “what” and “how much”. In addition, we can now provide both the template function and the overload, and both worlds are available to us:

 // length specified implementation void f(const char *s, std::size_t N) { // caller passed N } // fixed buffer template wrapper template<std::size_t N> void f(const char(&ar)[N]) { f(ar,N); // invokes length-specified implementation from above. } 

And both of them will work:

 int main() { char buff[3]; f(buff,3); f(buff); } 

So how is that good? Because the following will mean a compiler error, since the corresponding implementation cannot be found:

 int main() { char buff[3]; const char *ptr = buff; f(ptr); // ERROR: no matching function f(const char *) } 

In general, this is a general technique that helps to provide bullets to both points on our list: “what” and “how much”, without the need to use sizeof(ar)/sizeof(*ar) long time each time you use a fixed -length own array as your input parameter.

Good luck.

+11
source

Pointers do not store information about whether they point to a single object or the first object of an array. So if you, for example, write functions inside the body

 unsigned int f(const char *a); 

Expression

 sizeof( a ) / sizeof( *a ) 

you will only get the size of the pointer itself. Although if you used the same expression in the function body

 template<unsigned int N> unsigned int f(const char (&a)[N]); 

you get the size of the array (of course, you can use just the value of N).

So, when the second approach is used, usually such functions are declared by two parameters, where the second parameter indicates the size of the array

 unsigned int f(const char *a, size_t n); 

Consider a situation where you need to add an array of characters passed as an argument with another string (suppose that the const specifier is missing). When you use the second declaration, even the strlen function applied to the pointer will not help you determine if the original string that can be added is enough. In the first approach, you can use the expression N - strlen( a ) to determine if there is enough space in the array.

+3
source
 template<unsigned int N> unsigned int f(const char (&a)[N]); 

This function template takes an array by reference, which means that when used directly with statically assigned C-arrays, the function knows the size of the array when compiled.

 unsigned int f(const char *a); 

Here the whole function knows that it was given some pointer to const char. Therefore, if you want to use it to manipulate an array, it must take the size of the array as an additional argument, since there is no way to get this information only from the pointer.

+3
source

The version of the template takes an array of a certain size. The pointer version accepts only the pointer, without any concept of the size of the data the pointer points to.

+2
source

template in the instance for each individual value of N (therefore, additional machine code is generated), but inside f he can use his knowledge of N to perform any useful actions (for example, process the correct number of records from a , respectively, the size of another array of characters that may be needed ) You should look inside f to find out if N really used, and if it is useful. In the version without a template, the caller does not provide the size of the array / data, therefore there is no equivalent information (if the data a points to a variable in length, then f will have to rely on another way to find out how much data there is, for example strlen() , which takes time to find the final NUL).

+1
source

There are two reasons to use this:

  • it is impossible to pass this template function to anything but c-style arrays. Thus, calling this method with a pointer will be a compilation error.
  • in the template function you get the size of the array estimated at compile time

So this is:

 const char* p="abc"; f(p); 

compilation fails:

 garbage.cpp:39:5: error: no matching function for call to 'f(const char*&)' f(p); ^ garbage.cpp:39:5: note: candidate is: garbage.cpp:21:39: note: template<unsigned int N> unsigned int f(const char (&)[N]) template<unsigned int N> unsigned int f(const char (&a)[N]) 

While this:

 f("abc"); 

ok and the size of this array is defined as a compilation constant

+1
source

Source: https://habr.com/ru/post/1203785/


All Articles