I am learning the basics of C++ and OOP in my university now. I am not 100% sure how a function pointer works when assigning functions to them. I encountered the following code:
void mystery7(int a, const double b) { cout << "mystery7" << endl; }
const int mystery8(int a, double b) { cout << "mystery8" << endl; }
int main() {
void(*p1)(int, double) = mystery7; /* No error! */
void(*p2)(int, const double) = mystery7;
const int(*p3)(int, double) = mystery8;
const int(*p4)(const int, double) = mystery8; /* No error! */
}
From my understanding, the p2
and p3
assignments are fine as the function parameters types match and const-ness is correct. But why don't the p1
and p4
assignments fail? Shouldn't it be illegal to match const double/int to non-const double/int?
To pass the value by pointer, argument pointers are passed to the functions just like any other value. So accordingly you need to declare the function parameters as pointer types as in the following function swap(), which exchanges the values of the two integer variables pointed to by its arguments.
Pass-by-pointer means to pass a pointer argument in the calling function to the corresponding formal parameter of the called function. The called function can modify the value of the variable to which the pointer argument points. When you use pass-by-pointer, a copy of the pointer is passed to the function.
Declaring function parameters const indicates that the function promises not to change these values. In C, function arguments are passed by value rather than by reference. Although a function may change the values passed in, these changed values are discarded once the function returns.
According to the C++ Standard (C++ 17, 16.1 Overloadable declarations)
(3.4) — Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called.
So in the process of determining of the function type the qualifier const for example of the second parameter of the function declaration below is discarded.
void mystery7(int a, const double b);
and the function type is void( int, double )
.
Also consider the following function declaration
void f( const int * const p );
It is equivalent to the following declaration
void f( const int * p );
It is the second const that makes the parameter constant (that is it declares the pointer itself as a constant object that can not be reassigned inside the function). The first const defines the type of the pointer. It is not discarded.
Pay attention to that though in the C++ Standard there is used the term "const reference" references themselves can not be constant opposite to pointers. That is the following declaration
int & const x = initializer;
is incorrect.
While this declaration
int * const x = initializer;
is correct and declares a constant pointer.
There is a special rule for function arguments passed by value.
Although const
on them will affect their usage inside the function (to prevent accidents), it's basically ignored on the signature. That's because the const
ness of an object passed by value has no effect whatsoever on the original copied-from object at the call site.
That's what you're seeing.
(Personally I think that this design decision was a mistake; it's confusing and unnecessary! But it is what it is. Note that it comes from the same passage that silently changes void foo(T arg[5]);
into void foo(T* arg);
, so there's plenty of hokey bullsh!t in there already that we have to deal with!)
Do recall, though, that this doesn't just erase any const
in such an argument's type. In int* const
the pointer is const
, but in int const*
(or const int*
) the pointer is non-const
but is to a const
thing. Only the first example relates to const
ness of the pointer itself and will be stripped.
[dcl.fct]/5
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 ofT
” or of function typeT
is adjusted to be “pointer toT
”. 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. [ Note: This transformation does not affect the types of the parameters. For example,int(*)(const int p, decltype(p)*)
andint(*)(int, const int*)
are identical types. — end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With