Function Overloading
When we have multiple function definitions, we need to know which function will the compiler choose.
When the compiler picks the function, the return type is not considered, and only the signature matters. In other words, function name, number of parameters, and the types of each parameters will make the difference.
Here we have some candidates for a function call f('x');
- void f(int);
- float f(float, float = 911);
- void f(char);
- char *f(const char *);
- char f(const char &);
- template
void f(const T&); - template
void f(T*);
Here are the factors that decides the rankings of the candidates:
- Of course, exact match.
int exactly matches int &
char & exactly matches const char & - Regular functions win over template functions.
- Conversion via promotion:
char/short -> int
float -> double - Conversion via standard conversion
int -> char
long -> double
Let's follow the step of the selection:
- #4 and #7 are out because an integral type cannot be converted to a pointer type.
- #1 is better than #2 because char to int promotion while char to float is a standard promotion.
- #3, #5, and #6 are better than #1 and #2 since they are exact matches.
- #3 and #5 win over #6 because #6 is a template.
- With #3 and #5, we will have ambiguity. We will get a complain from our compiler.
As another example, what will be called from the call int n = add(float(3.14))?
- int add(int n)
- long add(long n)
- double add(double n)
Answer is #3 because we have a case for promotion from float to double. So, it will call #3 and the result will be converted to int.
Before we go over the effect of constness of parameter on the overloading, let's think about the overloading functions regarding the return type and signature of a function. As we already know return type is not recognized as a function signature. When we declare two functions whose return type and parameters match exactly, then our compiler think the second declaration is a redeclaration of the first. However, with different return types, it will be an declaration error of the second function if the parameters match exactly.
As shown in the code below, we need to know what const makes difference to the overloading functions, and also need to aware that there is additional constraint such as the parameter passed into the function.
The case (1) takes a non-const object while in the case (2) we have const object as a parameter. Between (3) and (4), we take an reference to an object.
Let's look at the following code:
struct A { int m_i; char m_c[6]; }; void f(A){} // (1) void f(const A){} // (2) Error in redeclaration void f(A &){} // (3) void f(const A &){} // (4) int main() { A objA = {100, "Hello"}; f(objA); return 0; }
- If we have only #3 and #4, #3 will be chosen because the call is from non-const type.
- However, between #1 and #2, we get an ambiguity complain from the compiler. That's because the preference between const and non-const only applies to reference or pointer type. The reason? It's simple. We copy the object whether it's a non-const or const. Compiler can't tell the difference. So, it complains.
Another minor thing we should know is:
we cannot overload based on whether the pointer itself const or not:
f(int *); f(int *const) // redeclaration
That's because no matter what the pointer type (const or non-const) is, the object passed in will be copied. Compiler cannot tell the difference.
When we call C functions from C++, we need to use extern "C". This is because C++ allows function overloading while C does not. As a result, C++ function names are mangled to get additonal information in the symbol table such as the number and type of each function parameter. So, C code compiled by C compiler cannot be called by C++ code. As an example, the code below gives a link time error:
error LNK2019: unresolved external symbol "void __cdecl c_func(int)" (?c_func@@YAXH@Z) referenced in function _main
Here is the code:
// main.cpp #include <iostream> #include "CHeader.h" #include "Header.h" int main() { int n = 10; c_func(n); cpp_func(n); cpp_func(n,n); return 0; } // CHeader.h void c_func(int); // Header.h #include <iostream> void cpp_func(int); void cpp_func(int,int); // cpp_funcs.cpp #include <iostream> using namespace std; void cpp_func(int a) { cout << "cpp_func(int)" << endl; } void cpp_func(int a, int b) { cout << "cpp_func(int, int)" << endl; } // c_func.c #include <stdio.h> void c_func(int a) { printf("void c_func(int a)\n"); }
To fix the problem, we must wrap our C API in an extern "C" construct in C header file. This will force the C++ compiler use C-style call convention for the functions contained the extern scope. So, the CHeader.h should be modified as shown below:
// CHeader.h #ifdef __cplusplus extern "C" { #endif void c_func(int); #ifdef __cplusplus } #endif
In summary, by wrapping extern "C" around C APIs, we make a C function-name called from C++ code to have 'C' linkage without any name mangling. The linkage block defined above, can be used to create a common C and C++ header.
Note:
- the __cplusplus directive will be automatically defined when we use C++ compiler.
We can find discussions on the extern "C" from stackoverflow.
- The extern "C" directive specifies only the linkage convention not the scope and storage class of variables.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization