Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I pass constant pointers disguised as arrays?

void foo(const char *s);

is equivalent to:

void foo(const char s[]);

Are there similar equivalents to the following two?

void foo(char * const s);
void foo(const char * const s);
like image 805
fredoverflow Avatar asked Oct 27 '11 07:10

fredoverflow


Video Answer


1 Answers

In C++, the compiler will automatically convert function parameters of type array of N elements of type T (where N can be unknown) into pointer to T. In the same transformation top level const qualifiers for the arguments are dropped:

void f( const int x ); // => declares: void f( int )
void f( int * const ); // => declares: void f( int* )

That means that in the pointer case, the top level const qualifier is removed, and in the case of the array it is converted to pointer to. Now, on the other hand you cannot mix both, only because you cannot declare a constant array of N elements of type T, since arrays are always const. That means that you cannot write:

void f( const int a[] const );  // Invalid type definition

As the type of the parameter is invalid. If it was a valid type, then the conversions would apply, but because it is not, the compiler will reject the code before trying to perform the conversion.

This is treated in §8.3.5/3 of the C++03 standard (and probably somewhere close in C++11)

A single name can be used for several different functions in a single scope; this is function overloading (clause 13). All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type. The type of a function is determined using the following rules. The type of each parameter is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, several transformations take place upon these types to determine the function type. Any cv-qualifier modifying a parameter type is deleted. [Example: the type void(*)(const int) becomes void(*)(int) —end example] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. If a storage-class-specifier modifies a parameter type, the specifier is deleted. [Example: register char* becomes char* —end example] Such storage-class-specifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. The resulting list of transformed parameter types is the function’s parameter type list.

Note that since the compiler will perform that conversion, it is better to write the actual type that is going to be used by the compiler, following the principle of least surprise:

void f( int a[10] ) { a[5] = 7; }

The compiler is not going to check that the passed array has 10 elements, it reads the declaration as void f( int * ), and will gladly accept a call with an array of less elements or even no array at all (a pointer to a single int). Using a pointer in the actual code:

void f( int *a ) { a[5] = 7; }

Will likely trigger some alarms in a code review: are we guaranteed that in all calls to f the argument will be at least 6 elements big? Should we not pass also the size just in case?

like image 182
David Rodríguez - dribeas Avatar answered Oct 02 '22 17:10

David Rodríguez - dribeas