Reputation: 181
I'm looking for where const-promotion is defined in c/c++. It's an implicit conversion, but I cannot find any documentation on it.
This works on g++ while using the --pedantic flag
// prototypes for example
void foo(char*);
void bar(const char*);
char buffer[8];
snprintf(buffer,sizeof(buffer), "hello");
// note: string literals are of type const char*
foo(buffer);
foo("hello"); // works, but why
bar(buffer);
bar("hello");
The behavior represented above is the expected behavior. However I am looking for the documentation for this behavior. I have looked at (drafts of) the c++98 standard and stack overflow searching for "promotion" and "implicit conversion" and have not found an answer.
If this question is too broad, I am using C++98, so we can address it for that standard.
Upvotes: 0
Views: 392
Reputation: 224310
This answer is for C and not C++.
Character string literals (distinguished from UTF-8 string literals or wide string literals) are arrays of char
, per C 2018 6.4.5 61. For historical reasons, they are not arrays of const char
, but they should be treated as const
by programmers as, if a program tries to write to a string literal, the behavior is not defined by the C standard.
As an array, a string literal is automatically converted to a char *
pointing to its first element, unless it is the operand of sizeof
or unary &
or is used to initialize an array.
Thus, in both foo(buffer)
and foo("hello")
, we have a char *
argument passed to a char *
parameter, and no conversion is necessary.
In bar(buffer)
and bar("hello")
, we have a char *
argument passed to a const char *
parameter. The explanation for this follows.
For function calls where a prototype is visible, the arguments are converted to the types of the parameters as if by assignment, per C 2018 6.5.2.2 7:
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.…
(Note that the “unqualified version of its declared type” means a const int
or char * const
parameter would be int
or char *
, respectively, not that a const char *
parameter would be char *
.)
6.5.16.1 2 says:
In simple assignment (=), the value of the right operand is converted to the type of the assignment expression…
The type of the assignment expression is that of the left operand, 6.5.16 3:
… The type of an assignment expression is the type the left operand would have after lvalue conversion.…
So now we know the char *
is converted to const char *
. This also satisfies the constraints for assignment in 6.5.16.1 1:
One of the following shall hold: … the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;…
And the pointer conversion is specified in 6.3.2.3 2:
For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.
For the snprintf
call, the argument "hello"
is passed in a location corresponding to ...
in the parameters. For this, we look to the rest of 6.5.2.2 7, which continues from the first part quoted above:
… The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.
The default argument promotions are in 6.5.2.2 6:
… the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.
Those promotions do not affect pointers, so the pointer is passed with its type unchanged. That is interesting because we could pass either a char *
or a const char *
here. The specification for snprintf
refers to fprintf
, which, for %s
specification, says in 7.21.6.1 8:
… the argument shall be a pointer to the initial element of an array of character type.…
So it just requires a pointer to “character type,” not a specific type such as char
or const char
or volatile char
.
(We might further wonder whether, if we were implementing our own function like snprintf
and using <stdarg.h>
to do it, whether passing a char *
argument and processing it with a va_arg(ap, const char *)
macro invocation would work. My initial reading of the va_arg
specification in 7.16.1.1 2 says the types must be compatible, but char *
and const char *
are not compatible, but I have not studied this thoroughly.)
1 Technically, a string literal is a thing in the source code or the representation of it during phases of C translation, and it is used to create an array of char
. For simplicity, I will refer to array as the string literal.
Upvotes: 4