When will default argument promotions happen? - c++

In C language, compiler perform default argument promotion when the function called does not have a prototype.
But how about C++? When will default argument promotions happen?
In C++11 standard 5.2.2/7:
When there is no parameter for a given argument, the argument is
passed in such a way that the receiving function can obtain the value
of the argument by invoking va_arg (18.10). [ Note: This paragraph
does not apply to arguments passed to a function parameter pack.
Function parameter packs are expanded during template instantiation
(14.5.3), thus each such argument has a corresponding parameter when a
function template specialization is actually called. —end note ] The
lvalue-to-rvalue (4.1), array-to-pointer (4.2), and
function-to-pointer (4.3) standard conversions are performed on the
argument expression. An argument that has (possibly cv-qualified) type
std::nullptr_t is converted to type void* (4.10). After these
conversions, if the argument does not have arithmetic, enumeration,
pointer, pointer to member, or class type, the program is ill-formed.
Passing a potentially-evaluated argument of class type (Clause 9)
having a nontrivial copy constructor, a non-trivial move constructor,
or a non-trivial destructor, with no corresponding parameter, is
conditionally-supported with implementation-defined semantics. If the
argument has integral or enumeration type that is subject to the
integral promotions (4.5), or a floating point type that is subject to
the floating point promotion (4.6), the value of the argument is
converted to the promoted type before the call. These promotions are
referred to as the default argument promotions.
This paragraph still does not specify when will a default argument promotion happen. This paragraph may talk too much without a clear logic. I strove to outline the logic but failed. I am not familiar with the invoking va_arg.
Hope you help me.

Default promotions will happen before the function is called, in the calling context.
If you're really asking about the circumstances under which default promotions are carried out, that's covered in the excerpt, though it's such a tiny piece that it's easy to miss: "When there is no parameter for a given argument...". In other words, it's essentially identical to the situation in C, with the exception that a C-style function declaration that doesn't specify parameter types simply doesn't exist in C++. Therefore, the only time you can have an argument without a parameter specifying its type is when a function has an explicit ellipsis, such as printf: int printf(char const *format, ...);.

From the very paragraph you quote in your question: "the value of the argument is converted to the promoted type before the call".
You say of C "default argument promotion when the function called does not have a prototype" - but remember that scenario doesn't exist in C++ - you can not call a function for which no declaration or definition has been seen.
The mention of "invoking va_arg" means that some of the argument promotions are applied when calling a function that will then access the values using the va_arg functions (see http://linux.die.net/man/3/va_arg). Think of it like this: one function call might pass the value int(3), another int(7777), yet another char(7) - how should the called function know what to expect? It will probably promote all values for that parameter to some largest-supported-integral type such as an int or long, then when va_arg is used within the function it will convert from int or long to whatever integral type the va_arg call specifies. This does mean, for example, that int(7777) value might be passed where only a char is expected and the value may be truncated to 8 bits without warning, but that's generally better than having the program crash because the number of bytes of data passed didn't match the number consumed, or some other weird side effect.

Related

Why are parameters promoted when it comes to a variadic function?

Why are parameters promoted when it comes to a variadic function,for instance floats are promoted to double ext and in which order are they promoted?
Variadic arguments - cppreference.com
Default conversions
When a variadic function is called, after lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions, each argument that is a part of the variable argument list undergoes additional conversions known as default argument promotions:
std::nullptr_t is converted to void*
float arguments are converted to double as in floating-point promotion
bool, char, short, and unscoped enumerations are converted to int or wider integer types as in integer promotion
Why are parameters promoted
Because that is how the language has been specified.
You may be thinking, why has the language been specified that way. I don't know if there is published rationale for this choice, but I suspect that the answer is as simple as: Because that is how the C language had been specified
You may be thinking, why was the C language specified that way. There is a standard document N1256 discussing design rationale of some choices for the C99 standard. It seems to not cover this choice. Besides, C language existed long before its standardisation and C99 wasn't even the first standard version. This behaviour may have existed before the involvement of the committee.
For what it's worth, same promotion rules apply also to calling functions that haven't been declared (until C99) or calling a fixed argument function through a prototype which doesn't declare the parameters:
// this is C lanugage
void fun();
int main(int, char [][]) {
float f = 42;
fun(f); // argument promotes to double
undeclared(f); // ill-formed since C99
// argument promotes to double prior to C99
The reasons for this may be similar to the reasons for promotion in case of variable parameter lists.
Promotion of arguments for variadic functions make it way more easier to deal with them. Since the function code doesn't know the actual type of arguments from the function signature, calling has to communicate the type through some other means, and promotion reduces the number of options without sacrificing the flexibility.
For example, consider classical example of variadic function - printf. When you give it %f argument, it already knows that the argument is double precision, since it would be promoted. Absence promotion, two different modifiers would have to exist, one for single precision and another one for double precision.
Another example would be integral promotions. Currently any type would work with %d modifier, and while modifiers for short versions do exist, one is not required to use them, and can simplify their code.
In addition, it provides for fewer surprises when using some other variadic functions. For example, Posix open function is shown as if it would be an overloaded function with either 2 or 3 arguments, last argument being specified in the man as mode_t type. In fact, there are no overloads in C, so there are no two versions of open - there is only one, which is a variadic one.
Absent of promotions, one would have to make sure that when 3-argument version is used, the last argument is exactly mode_t type, which would be quite inconvenient, counterintuitive and failure to do so would likely lead to quite unexpected behavior. Automatic promotions save us from this.

"Ambiguous conversion sequence" - what is the purpose of this concept?

In N4659 16.3.3.1 Implicit conversion sequences says
10 If several different sequences of conversions exist that each convert the argument to the parameter type, the implicit conversion sequence associated with the parameter is defined to be the unique conversion sequence designated the ambiguous conversion sequence. For the purpose of ranking implicit conversion sequences as described in 16.3.3.2, the ambiguous conversion sequence is treated as a user-defined conversion sequence that is indistinguishable from any other user-defined conversion sequence [Note: This rule prevents a function from becoming non-viable because of an ambiguous conversion sequence for one of its parameters.] If a function that uses the ambiguous conversion sequence is selected as the best viable function, the call will be ill-formed because the conversion of one of the arguments in the call is ambiguous.
(The corresponding section of the current draft is 12.3.3.1)
What is the intended purpose of this rule and the concept of ambiguous conversion sequence it introduces?
The note supplied in the text states that the purpose of this rule is "to prevent a function from becoming non-viable because of an ambiguous conversion sequence for one of its parameters". Um... What does this actually refer to? The concept of a viable function is defined in the preceding sections of the document. It does not depend on ambiguity of conversions at all (conversions for each argument must exist, but they don't have to be unambiguous). And there seems to be no provision for a viable function to somehow "become non-viable" later (neither because of some ambiguity nor anything else). Viable functions are enumerated, they compete against each other for being "the best" in accordance with certain rules and if there's a single "winner", the resolution is successful. At no point in this process a viable function may (or needs) to turn into a non-viable one.
The example provided within the aforementioned paragraph is not very enlightening (i.e. it is not clear what role the above rule plays in that example).
The question originally popped up in connection with this simple example
struct S
{
operator int() const { return 0; };
operator long() const { return 0; };
};
void foo(int) {}
int main()
{
S s;
foo(s);
}
Let's just mechanically apply the above rule here. foo is a viable function. There are two implicit conversion sequences from argument type S to parameter type int: S -> int and S -> long -> int. This means that per the above rule we have to "pack" them into a single ambiguous conversion sequence. Then we conclude that foo is the best viable function. Then we discover that it uses our ambiguous conversion sequence. Consequently, per the above rule the code is ill-formed.
This seems to make no sense. The natural expectation here is that S -> int conversion should be chosen, since it is ranked higher than S -> long -> int conversion. All compilers I know follow that "natural" overload resolution.
So, what am I misunderstanding?
To be viable, there must be an implicit conversion sequence.
The standard could have permitted multiple implicit conversion sequences, but that might make the wording for determining which overload to select more complex.
So the standard ends up defining one and exactly one implicit conversion sequence for each argument to each parameter. In the case where there is ambiguity, the one it uses is the ambiguous conversion sequence.
Once this is done, it no longer has to deal with the possibility of multiple conversion sequences of one argument to one parameter.
Imagine if we where writing this in C++. We might have a few types:
namespace conversion_sequences {
struct standard;
struct user_defined;
struct ellipsis;
}
where each has plenty of stuff in them (skipped here).
As each conversion sequence is one of the above, we define:
using any_kind = std::variant< standard, user_defined, ellipsis >;
Now, we run into the case of there being more than one conversion sequence for a given argument and parameter. We have two choices at this point.
We could pass around a using any_kinds = std::vector<any_kind> for a given argument,parameter pair, and ensure all of the logic that handles picking a conversion sequence deals with this vector...
Or we could note that the handling of a 1 or more entry vector never looks at the elements in the vector, and it is treated exactly like a kind of user_defined conversion sequence, until the very end at which point we generate an error.
Storing that extra state, and having the extra logic, is a pain. We know we don't need that state, and we don't need code to handle the vector. So we just define a sub-type of conversion_sequence::user_defined, and the code after it finds the preferred overload it checks if the resulting overload chosen should generate errors.
While the C++ standard is not (always) implemented in C++ and the wording doesn't have to have a 1:1 relationship with its implementation, writing a robust standard document is a kind of coding, and some of the same concerns apply.

C++ conditional operator with void operand(s)

I’m trying to understand the following excerpt from the C++ standard (ISO/IEC 14882:2003, newer versions say essentially the same):
5.16 Conditional operator
2 If either the second or the third operand has type (possibly cv-qualified) void, then the lvalue-to-rvalue
(4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second
and third operands, ...
I am inclined to thinking that in this context, when an operand is a function call, then the type of the operand is taken to be (although it is not) the function return type. If that is so, then that yields an example of a void type.
I also imagine that a throw expression could also be surmised to have type void in this context, independently of the type of the throw operand. That would then be another example.
Are my two assumptions right? Are there any other cases?
Many thanks
About throw, yes, there is no result, so the type is void and the type of the throw operand is irrelevant. I'm not sure how it could be relevant so that question seems odd.
About functions, I don't know why you say the type of the operand is not the function return type if the operand is a function call. What else would it be? It's where the operand is a function (as opposed to a function call) that function-to-pointer conversion would kick in.
The result type of the conditional operator depends on the type of it's operands.
I think the special language around one of the operands being void is simply that the result of the conditional operator is then void, so the rules about possibly performing conversions of operands of different types are not relevant.

Incompatible operand types when using ternary conditional operator

This code:
bool contains = std::find(indexes.begin(), indexes.end(), i) != indexes.end();
CardAbility* cardAbility = contains ? new CardAbilityBurn(i) : new CardAbilityEmpty;
gives me the following error:
Incompatible operand types CardAbilityBurn and CardAbilityEmpty
However if I write the code like this:
if (contains)
{
cardAbility = new CardAbilityBurn(i);
}
else
{
cardAbility = new CardAbilityEmpty;
}
then the compiler doesn't mind. Why so? I want to use ternary conditional operator because it is just one line. What's wrong there?
I need to note (I think you might need this information) that CardAbilityEmpty and CardAbilityBurn both derive from CardAbility so they are so to say brothers.
Thanks
C++'s type system determines expressions' types from the inside out[1]. That means that the conditional expression's type is determined before the assignment to CardAbility* is made, and the compiler has to choose with only CardAbilityBurn* and CardAbilityEmpty*.
As C++ features multiple inheritance and some more possible conversion paths, since none of the types is a superclass of the other the compilation stops there.
To compile successfully, you need to provide the missing part : cast one or both operands to the base class type, so the conditional expression as a whole can take that type.
auto* cardAbility = contains
? static_cast<CardAbility*>(new CardAbilityBurn(i))
: static_cast<CardAbility*>(new CardAbilityEmpty );
(Note the use of auto, since you already provided the destination type in the right-side expression.)
It is however a bit convoluted, so in the end the if-else structure is better-suited in this case.
[1] There is one exception : overloaded function names have no definitive type until you convert them (implicitly or explicitly) to one of their versions.
There are several cases described for Microsoft compilers, how to handle operand types.
If both operands are of the same type, the result is of that type.
If both operands are of arithmetic or enumeration types, the usual
arithmetic conversions (covered in Arithmetic Conversions) are performed to
convert them to a common type.
If both operands are of pointer types or if one is a pointer type and the
other is a constant expression that evaluates to 0, pointer conversions are
performed to convert them to a common type.
If both operands are of reference types, reference conversions are
performed to convert them to a common type.
If both operands are of type void, the common type is type void.
If both operands are of the same user-defined type, the common type is
that type.
If the operands have different types and at least one of the operands
has user-defined type then the language rules are used to
determine the common type. (See warning below.)
And then there is a caution:
If the types of the second and third operands are not identical, then
complex type conversion rules, as specified in the C++ Standard, are
invoked. These conversions may lead to unexpected behavior including
construction and destruction of temporary objects. For this reason, we
strongly advise you to either (1) avoid using user-defined types as
operands with the conditional operator or (2) if you do use
user-defined types, then explicitly cast each operand to a common
type.
Probably, this is the reason, Apple deactivated this implicit conversion in LLVM.
So, if/else seems to be more appropriate in your case.

Widening of integral types?

Imagine you have this function:
void foo(long l) { /* do something with l */}
Now you call it like so at the call site:
foo(65); // here 65 is of type int
Why, (technically) when you specify in the declaration of your function that you are expecting a long and you pass just a number without the L suffix, is it being treated as an int?
Now, I know it is because the C++ Standard says so, however, what is the technical reason that this 65 isn't just promoted to being of type long and so save us the silly error of forgetting L suffix to make it a long explicitly?
I have found this in the C++ Standard:
4.7 Integral conversions [conv.integral]
5 The conversions allowed as integral promotions are excluded from the set of integral conversions.
That a narrowing conversion isn't being done implicitly, I can think with, but here the destination type is obviously wider than the source type.
EDIT
This question is based on a question I saw earlier, which had funny behavior when you didn't specify the L suffix. Example, but perhaps it's a C thing, more than C++?!!
In C++ objects and values have a type, that is independent on how you use them. Then when you use them, if you need a different type it will be converted appropriately.
The problem in the linked question is that varargs is not type-safe. It assumes that you pass in the correct types and that you decode them for what they are. While processing the caller, the compiler does not know how the callee is going to decode each one of the arguments so it cannot possibly convert them for you. Effectively, varargs is as typesafe as converting to a void* and converting back to a different type, if you get it right you get what you pushed in, if you get it wrong you get trash.
Also note that in this particular case, with inlining the compiler has enough information, but this is just a small case of a general family if errors. Consider the printf family of functions, depending on the contents of the first argument each one of the arguments is processed as a different type. Trying to fix this case at the language level would lead to inconsistencies, where in some cases the compiler does the right thing or the wrong one and it would not be clear to the user when to expect which, including the fact that it could do the right thing today, and the wrong one tomorrow if during refactoring the function definition is moved and not available for inlining, or if the logic of the function changes and the argument is processed as one type or another based on some previous parameter.
The function in this instance does receive a long, not an int. The compiler automatically converts any argument to the required parameter type if it's possible without losing any information (as here). That's one of the main reasons function prototypes are important.
It's essentially the same as with an expression like (1L + 1) - because the integer 1 is not the right type, it's implicitly converted to a long to perform the calculation, and the result is a long.
If you pass 65L in this function call, no type conversion is necessary, but there's no practical difference - 65L is used either way.
Although not C++, this is the relevant part of the C99 standard, which also explains the var args note:
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. 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.
Why, (technically) when you specify in the declaration of your function that you are expecting a long and you pass just a number without the L suffix, is it being treated as an int?
Because the type of a literal is specified only by the form of the literal, not the context in which it is used. For an integer, that is int unless the value is too large for that type, or a suffix is used to specify another type.
Now, I know it is because the C++ Standard says so, however, what is the technical reason that this 65 isn't just promoted to being of type long and so save us the silly error of forgetting L suffix to make it a long explicitly?
The value should be promoted to long whether or not you specify that type explicitly, since the function is declared to take an argument of type long. If that's not happening, perhaps you could give an example of code that fails, and describe how it fails?
UPDATE: the example you give passes the literal to a function taking untyped ellipsis (...) arguments, not a typed long argument. In that case, the function caller has no idea what type is expected, and only the default argument promotions are applied. Specifically, a value of type int remains an int when passed through ellipsis arguments.
The C standard states:
"The type of an integer constant is the first of the corresponding list in which its value can be represented."
In C89, this list is:
int, long int, unsigned long int
C99 extends that list to include:
long long int, unsigned long long int
As such, when you code is compiled, the literal 65 fits in an int type, and so it's type is accordingly int. The int is then promoted to long when the function is called.
If, for instance, sizeof(int) == 2, and your literal is something like 64000, the type of the value will be a long (assuming sizeof(long) > sizeof(int)).
The suffixes are used to overwrite the default behavior and force the specified literal value to be of a certain type. This can be particularly useful when the integer promotion would be expensive (e.g. as part of an equation in a tight loop).
We have to have a standard meaning for types because for lower level applications, the type REALLY matters, especially for integral types. Low level operators (such as bitshift, add, ect) rely on the type of the input to determine overflow locations. ((65 << 2) with integers is 260 (0x104), but with a single char it is 4! (0x004)). Sometimes you want this behavior, sometimes you don't. As a programmer, you just need to be able to always know what the compiler is going to do. Thus the design decision was made to make the human explicitly declare the integral types of their constants, with "undecorated" as the most commonly used type, integer.
The compiler does automatically "cast" your constant expressions at compile time, such that the effective value passed to the function is long, but up until the cast it is considered an int for this reason.