Function args declared as consts became non constant with definition [duplicate] - c++

This question already has answers here:
Why does a function declaration with a const argument allow calling of a function with a non-const argument?
(4 answers)
Closed 5 years ago.
Code
class A
{
public:
void f(const int i);
};
void A::f(int i)
{
std::cout<<++i<<std::endl;
}
int main()
{
A a;
a.f(1);
}
Why compiler does not give an error in this case ? Why the definition overrides the constant argument ?
Also, when the argument is of type reference (&) the compiler throws error but why not in this case ?
Is there any compiler flag to enable warning on these mentioned cases ?
I am interested more from compiler error POV. Because one can easily put declaration (const) and definition(non-constant) differently and still compiler accepts it. If a mistake can be made it will eventually be made. Why can't compiler complain when there is such difference present.

From 11.3.5 Functions [dcl.fct]:
A single name can be used for several different functions in a single
scope; this is function overloading (Clause 16). All declarations for
a function shall agree exactly in both the return type and the
parameter-typelist. The type of a function is determined using the
following rules. The type of each parameter (including function
parameter packs) 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 of function type T is adjusted to be
“pointer to T”. After producing the list of parameter types, any
top-level cv-qualifiers modifying a parameter type are deleted when
forming the function type. The resulting list of transformed parameter
types and the presence or absence of the ellipsis or a function
parameter pack is the function’s parameter-type-list.
Basically, it means, that const int and int are interchangeable in the context of your question.
On the other hand, if you add a reference, like const int&, const is not a top level cv-qualifier anymore (it marks the referenced value as const), so the compiler complains.
Usually, const is added to the definition in order to emphasise that the parameter is not changed inside the function body.

The function declaration
void f(const T arg);
is same as
void f(T arg);
However,
void f(const T& arg);
is not the same as
void f(T& arg);
and
void f(const T* arg);
is not the same as
void f(T* arg);
void f(const T arg);
void f(T arg);
are same because top-level cv-qualifiers are removed for function resolution purposes. There, the const-ness is applied to the top-level type, T.
OTOH,
void f(const T& arg);
void f(T& arg);
are not same since the const-ness is applied to the object being referred to by the reference arg, not to arg itself which is just a top-level thing.

Related

How can I receive a void parameter in function definition, C or C++?

How can I receive in a function definition when the one of the parameter in function call is void?
The scenario is that one of my projects, in a function call one of the parameters is void. That section I could not change.
I can change only the function declaration or the function definition, but I still confused what I need to change.
Please help
Below code is not a executable one:
for ex:
#include <stdio.h>
void test(void, int a) {
printf("%d", a);
}
int main() {
test(void, 32);
}
You can't. That is not valid C:
<source>:2:11: error: 'void' must be the only parameter
2 | void test(void,int a)
| ^~~~
Same goes for C++:
<source>:2:11: error: invalid use of type 'void' in parameter declaration
2 | void test(void,int a)
| ^~~~
The only valid use of void as parameter is on it's own, as in:
int bar();
int foo(void);
In C this means the function bar takes an undetermined number of arguments while foo does not take any arguments. In C++ both are equivalent.
void is an incomplete type and cannot be used as the parameter of a function(in both C and C++) with the exception that if void is the only parameter of that function. This can be seen from the following quoted statements.
C++
From function's documentation:
Parameter list determines the arguments that can be specified when the function is called. It is a comma-separated list of parameter declarations, each of which has the following syntax:
5 Indicates that the function takes no parameters, it is the exact synonym for an empty parameter list: int f(void); and int f(); declare the same function. Note that the type void (possibly cv-qualified) cannot be used in a parameter list otherwise: int f(void, int); and int f(const void); are errors (although derived types, such as void* can be used). In a template, only non-dependent void type can be used (a function taking a single parameter of type T does not become a no-parameter function if instantiated with T = void).
Note also that there is a semantic difference(between C++ & C) in case void is used as a parameter of a function and is the only parameter as shown in the following example:
int func(void); //declaration of func that takes no parameter and has the return type of void
int foo(); //declaration of foo that takes no parameter and has the return type of void
C
From function declaration's documentation:
parameters cannot have type void (but can have type pointer to void). The special parameter list that consists entirely of the keyword void is used to declare functions that take no parameters.
int f(void); // OK
int g(void x); // Error
Unlike in C++ and function definitions (since C23), the declarators f() and f(void) have different meaning: the declarator f(void) is a new-style (prototype) declarator that declares a function that takes no parameters. The declarator f() is a declarator that declares a function that takes unspecified number of parameters (unless used in a function definition)

Why template function with 'const' from left of parameter type is misbehaving against the rule of type deduction for pointer argument?

Consider this pseudo code for a type deduction case:
template<typename T> void f(ParamType param);
Call to function will be:f(expr);
According to type deduction case where ParamType is not a reference, pointer, nor a universal reference
(see S. Meyers "Effective Modern C++", p.14), but passed by value, to determine type T, one needs firstly
to ignore the reference and const part of 'expr' and then pattern-match exprs type to determine T.
The driver will be:
void PerformTest() {
int i = 42;
int* pI = &i;
f_const_left(pI);
f_non_template_left(pI);
f_const_right(pI);
f_non_template_right(pI);
}
Now consider these functions, which, using this deduction rule, are showing some counter-intuitive results while being called with pointer as an argument:
template<typename T> void f_const_left(const T t) {
// If 'expr' is 'int *' then, according to deduction rule for value parameter (Meyers p. 14),
// we need to get rid of '&' and 'const' in exp (if they exist) to determine T, thus T will be 'int *'.
// Hence, ParamType will be 'const int *'.
// From this it follows that:
// 1. This function is equivalent to function 'func(const int * t){}'
// 2. If ParamType is 'const int *' then we have non-const pointer to a const object,
// which means that we can change what pointer points to but cant change the value
// of pointer address using operator '*'
*t = 123;// compiler shows no error which is contradiction to ParamType being 'const int *'
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// As we see, consequence 2. is not satisfied:
// T is straight opposite: instead of being 'const int *'
// T is 'int const *'.
// So, the question is:
// Why T is not 'const int*' if template function is f(const T t) for expr 'int *' ?
}
Consider consequence 1.:
Lets create an equivalent non-template function:
void f_non_template_left(const int* t) {
// 1. Can we change the value through pointer?
*t = 123; // ERROR: expression must be a modifiable lvalue
// 2. Can we change what pointers points to?
t = nullptr; // NO ERROR
// As we can see, with non-template function situation is quite opposite.
}
For for completeness of the experiment, lets also consider another pair of functions but with 'const' being placed from the right side of a T: one template function and its non-template equivalent:
template<typename T> void f_const_right(T const t) {
// For expr being 'int *' T will be 'int *' and ParamType will be 'int * const',
// which is definition of a constant pointer, which cant point to another address,
// but can be used to change value through '*' operator.
// Lets check it:
// Cant point to another address:
t = nullptr; // compiler shows error that we cant assign to a variable that is const
// Can be used to change its value:
*t = 123;
// So, as we see, in case of 'T const t' we get 'int * const' which is constant pointer, which
// is intuitive.
}
Finally, the non-template function with 'const' from the right side of type:
void f_non_template_right(int* const t) {
// 1. Can we change the value through pointer?
*t = 123; // No errors
// 2. Can we change what pointers points to?
t = nullptr; // ERROR: you cant assign to a variable that is const
// As we can see, this non-template function is equivalent to its template prototype
}
Can someone explain why there is such insonsistency between template and non-template functions ?
And why template function with 'const' on the left is behaving not according to the rule of deduction?
// Hence, ParamType will be 'const int *'.
No, it will not (and not just because ParamType is not used in the code. For the purposes of this answer, let's pretend you meant T, which is there).
int * is a sequence of two tokens. One of those tokens is a typename (int), and the other is *. When combined in this way, the two tokens name a single type: pointer to an int.
const int is a sequence of two tokens. When combined, they name the type: const int.
const int* is a sequence of 3 tokens. When combined as such, they name a single type. The rules of C++ type naming are such that the const applies "const-ness" to the type specified by whatever is to the immediate left of it. If nothing is to its left, it applies to what is immediately to the right. So if you consider this as an expression, it is read as (const int)*. Therefore, this token sequence names the type: pointer to a const int.
const T, where T is a typename (which could be a template parameter, a type-alias, or the name of a type), is a sequence of two tokens. When combined, they name a single type: const (whatever type T designates).
If the type T happens to name is int*, then the type T designates is 'pointer to int', as previously discussed. Therefore, const T designates 'const (pointer to int)' Note that the pointer is what is const, not the int being pointed to.
Substitution of type aliases and template parameters is not done by copying-and-pasting tokens. It is done by applying the rules of typenames to the alias as a single unit. Whatever T is, the qualifiers like constapply to T as a unit.
int * const is a sequence of three tokens. Per the aforementioned rules, const is applied to whatever is to its left, if anything. So const applies to the *. Therefore, these tokens name the type: const pointer to int.
T const is a sequence of two tokens. When combined as such, they name a single type: const (whatever type T designates).
This is why T const and int * const behave the same, but const T and const int * don't.
(Referencing the C++14 Standard)
Your f_non_template_* functions aren't entirely correct.
Since T is a template parameter, it'll behave as if it is a unique type:
14.5.6.2 Partial ordering of function templates
(3) To produce the transformed template, for each type, non-type, or template template parameter (including
template parameter packs (14.5.3) thereof) synthesize a unique type, value, or class template respectively
and substitute it for each occurrence of that parameter in the function type of the template.
So to correctly test this your non-template functions would need to defined like this:
using TT = int*;
void f_non_template_left(const TT t) {
/* ... */
}
void f_non_template_right(TT const t) {
/* ... */
}
godbolt example
at which point you'll get exactly the same behaviour as with the templated functions.
Why it works that way
In this case T would be deduced to int*, which as a unique type would be a compound type:
3.9.2 Compound types
(1) Compound types can be constructed in the following ways:
[...]
(1.3) — pointers to void or objects or functions (including static members of classes) of a given type
[...]
And the cv-rules for compound types are as following:
3.9.3 CV-qualifiers
(1) A type mentioned in 3.9.1 and 3.9.2 [Compound Type] is a cv-unqualified type. Each type which is a cv-unqualified complete
or incomplete object type or is void (3.9) has three corresponding cv-qualified versions of its type: a constqualified version, a volatile-qualified version, and a const-volatile-qualified version.
(2) A compound type (3.9.2) is not cv-qualified by the cv-qualifiers (if any) of the types from which it is compounded. Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4).
So your cv-qualifiers for T in your template function refer to the top-level constness of the compound-type T in both your cases, so
template<typename T> void f_const_left(const T t);
template<typename T> void f_const_right(T const t);
are actually equivalent.
The only exception to this would be if T would be an array-type, in which case the cv-qualifier would apply to the elements of the array instead.
If you want to specify the constness of the pointed-to value, you could do it like this:
//const value
template<class T>
void fn(const T* value);
// const pointer
template<class T>
void fn(T* const value);
// const value + const pointer
template<class T>
void fn(const T* const value);

Function Matching for parameters of type const T& and T

I have a question regarding the c++ function matching for parameters of types T and const T&.
Let's say I have the following two functions:
void f(int i) {}
void f(const int &ri) {}
If I call f with an argument of type const int then this call is of course ambiguous. But why is a call of f with an argument of type int also ambiguous? Wouldn't be the first version of f be an exact match and the second one a worse match, because the int argument must be converted to a const int?
const int ci = 0;
int i = 0;
f(ci); // of course ambiguous
f(i); // why also ambiguous?
I know that such kind of overloading doesn't make much sense, because calls of f are almost always ambiguous unless the parameter type T doesn't have an accessible copy constructor. But I'm just studying the rules of function matching.
Regards,
Kevin
EDIT: To make my question more clear. If I have the two functions:
void f(int *pi) {}
void f(const int *pi) {}
Then the following call is not ambiguous:
int i = 0;
f(&i); // not ambiguous, first version f(int*) chosen
Although both versions of f could be called with &i the first version is chosen, because the second version of f would include a conversion to const. That is, the first version is a "better match". But in the two functions:
void f(int i) {} and
void f(const int &ri) {}
This additional conversion to const seems to be ignored for some reason. Again both versions of f could be called with an int. But again, the second version of f would require a conversion to const which would make it a worse match than the first version f(int).
int i = 1;
// f(int) requires no conversion
// f(const int &) does require a const conversion
// so why are both versions treated as "equally good" matches?
// isnt this analogous to the f(int*) and f(const int*) example?
f(i); // why ambiguous this time?
One call involves an "lvalue-to-rvalue conversion", the other requires an identity conversion (for references) or a "qualification adjustment" (for pointers), and according to the Standard these are treated equally when it comes to overload resolution.
So, neither is better on the basis of differing conversions.
There is, however, a special rule in the Standard, section 13.3.3.2, that applies only if both candidates being compared take the parameter by reference.
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if ... S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
There's an identical rule for pointers.
Therefore the compiler will prefer
f(int*);
f(int&);
over
f(const int*);
f(const int&);
respectively, but there's no preference for f(int) vs f(const int) vs f(const int&), because lvalue-to-rvalue transformation and qualification adjustment are both considered "Exact Match".
Also relevant, from section 13.3.3.1.4:
When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion.
The second call f(i) is also ambiguous because void f(const int &ri) indicates that ri is a reference to i and is a constant. Meaning it says that it will not modify the original i which is passed to that function.
The choice whether to modify the passed argument or not is in the hands of the implementer of the function not the client programmer who mearly uses that function.
The reason the second call f(i) is ambiguous is because to the compiler, both functions would be acceptable. const-ness can't be used to overload functions because different const versions of functions can be used in a single cause. So in your example:
int i = 0;
fi(i);
How would the compiler know which function you intended in invoking? The const qualifier is only relevant to the function definition.
See const function overloading for a more detailed explanation.

c++ template function overlading argument deduction with const argument

I am having the following setup:
template <typename T>
void foo(T& t);
void foo(const int& t);
void f()
{
int i;
foo(i); //Unresolved reference to "void foo<int>(int &)"
foo(const_cast<const int&>(i)); //Unresolved reference to "void foo(int const &)"
}
In the first call to foo, the compiler tries to call the template version, since the argument of the non-template one does not match the type of i. In the second call the non-template version is called. I am using the Microsoft C++ compiler version 10. Is this standard behavior? If the type is not exactly matched, even if it only has a const modifier, then the template function is called?
EDIT: I know those two functions don't have definition, I am just pointing out what the linker complains about, to make it more clear what the compiler wants to call.
Yes, this behavior is correct according to the C++11 Standard.
In the first case, the argument is a reference to a non-const integer. Both overloads are viable to resolve this call, but the function template allows a perfect match, while the non-template overload requires a qualification conversion.
In the second case, both are a perfect match, but one of the overloads is not a function template, and therefore it is a better candidate than the function template. Per § 13.3.3/1, in fact:
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the
standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the
entity being initialized) is a better conversion sequence than the standard conversion sequence from
the return type of F2 to the destination type. [ ... ] or, if not that,
— F1 is a non-template function and F2 is a function template specialization, or, if not that,
— [...]
Is this standard behavior? If the type is not exactly matched, even if it only has a const modifier, then the template function is called?
Yes, that is well-defined by the Standard.
If there is no EXACT match, the template is used, because instantiated template version is always a better match than the one which requires conversion (even be it int & to int const& conversion).
This should work
#include <iostream>
template <typename T>
void foo(T& t) {}
void foo(const int& t){}
void f()
{
int i;
foo(i); //Unresolved reference to "void foo<int>(int &)"
foo(const_cast<const int&>(i)); //Unresolved reference to "void foo(int const &);
}
int main()
{
f();
}

Why are argument modifiers (i.e., 'const' or 'volatile') not considered part of a function's type or signature?

Note that the following two functions have the same type and signature:
void foo1(int t) {} // foo1 has type 'void(*)(int)', and signature '(*)(int)'
void foo2(const int t) {} // Also type 'void(*)(int)', signature '(*)(int)'
(the const is not part of the function type or function signature). Similarly, a modifier (const or volatile) on the return type does not influence the function type or function signature.
However, in the function definition itself (not shown), the named variable t does maintain the const qualification in foo2.
There are many StackOverflow questions discussing why the return type of the function is not considered as part of the function signature (used for overload resolution).
However, I cannot find any StackOverflow question that asks why argument modifiers (const or volatile) are not part of the function's type or signature. Also, I have looked directly in the C++11 standards document and find it difficult to unravel.
What is the rationale behind the fact that argument modifiers (i.e., const and volatile) are not part of a function's type or signature?
ADDENDUM For clarity, from R.MartinhoFernandes's answer below, I should clarify that in C++ (I think) the argument modifiers const and volatile are only ignored as part of the function type/signature if they are top-level modifiers - see that answer below.
What is the rationale behind the fact that argument modifiers (i.e., const and volatile) are not part of a function's type or signature?
From a caller's perspective, there is no difference between void foo(int) and void foo(int const). Whatever you pass to it will not be modified, regardless of the modifier: the function will get a copy.
From an implementer's perspective the sole difference is that with void foo(int x) you can mutate x (i.e. your local copy) in the body, but you cannot mutate x with void foo(int const x).
C++ acknowledges these two perspectives. The caller's perspective is acknowledged by making the two declarations void foo(int); and void foo(int const); declare the same function. The implementer's perspective is acknowledged by allowing you to declare a function as void foo(int x); but define it as void foo(int const x) { /*...*/ } if you want to make sure you don't accidentally assign to the argument.
Note that this only applies for top-level const, i.e. const that applies to the whole type. In things like int const& or int const* the modifier only applies to a part of the type, as "pointer to (const (int))", so it is not top-level const. In int *const however, the const again applies to the whole type as in "const (pointer to (int))".