Function is a set of statements collected and given a name. It results shorter, simpler and managable program.
Function declaration is forward reference. Function prototype must include parameter list.
In C++, returntype Function();
is equivalent
to returntype Function(void);
, but
returntype Function(unknown);
in C. To ease
porting to C, declare void
explicity.
Better use value returning function rather than passing output parameter by reference.
returntype1 Function1(parametertype1 parameter); returntype2 Function2(returntype1 (*FunctionPointer)(parametertype1), parametertype1 parameter) { // executing using (*FunctionPointer)(parameter) or FunctionPointer(parameter) } int main(void) { Function2(Function1); // implicitly means returntype1 (*FunctionPointer)(parametertype1) = Function1; return 0; }Function name can be treated as address of first statement of the function. So
FunctionPointer = Function1
, or
FunctionPointer = &Function1
is a pointer to
address of first statement of any function which return
returntype
and have parameter with type
parametertype
, such as Function1
.
It can point to other function later. To simplify declaration
use typedef
. Function pointer can be initialized
with or assigned NULL, which indicates that the pointer does
not point to any function.
typedef returntype1 (*FunctionType)(parametertype1); returntype2 Function2(FunctionType function, parametertype1 parameter) { // executing using (*function)(parameter) or function(parameter) }Array of pointers is declared as
pointertype *array[POINTERNUM];
, so array of
pointers to function is
returntype1 Function1(parametertype1 parameter); returntype1 Function2(parametertype1 parameter); int main(void) { returntype1 (*FunctionPointer[])(parametertype1) = { Function1, Function2 }; // access using (*FunctionPointer[0])(parameter) // or FunctionPointer[1](parameter) return 0; } // using typedef typedef returntype1 (*FunctionType1)(parametertype1); FunctionType1 FunctionPointer[] = { Function1, Function2 };
int printf(const char*, ...); int strlen(const char*); int (*pfce)(const char*, ...); // can point to printf() int (*pfc)(const char*); // can point to strlen() // function type for reference parameter int (*pf)(const string&, const string&);
int (*rpf(int))(int*, int); // return int (*)(int*, int); // using typedef typedef int (*FunctionPointer)(int*, int); FunctionPointer rpf(int);
#define Function(parameter) ...
because
#define min(a,b) ((a) < (b) ? (a) : (b)) const int size = 10; int ia[size]; int main() { int elem_cnt = 0; int *p = &ia[0]; // count the number of array elements while ( min(p++,&ia[size]) != &ia[size] ) ++elem_cnt; cout << "elem_cnt : " << elem_cnt << "\texpecting: " << size << endl; return 0; }It returns 5 instead 10 because
p++
applied
twice with each expansion.
// in "someheader.h" #define alpha 'a' #define beta b[2] // in other place #include "someheader.h" struct S { int alpha; // becomes 'a' int beta; // becomes b[2] };
To acheive as generic as macro, use function template. Other C++ features that can avoid macro include inline functions, constructors (for initialization), destructors (for cleanup), exceptions (for exiting contexts), etc.
Because bool
is a distinct type, it can be
overloaded with int
(error on older compilers).
typedef
provides only alternative name for
existing data type; it does not create a new data type.
Therefore, function parameter lists that differ only in that
one uses a typedef and the other uses the type to which the
typedef corresponds are not valid overloading (but
redeclartion).
But enumeration defines a unique type that matches exactly only the enumerators within the enumeration and the objects declared to be of the enumeration.
const
or volatile
qualifier is not
taken into account as valid overloading. It is because they
are relevant only within the definition of function, it
does not change in any way the kind of arguments that can
be passed to the function. Any argument of type
int
can be used in a call to the function
f(int)
as well as the function
f(const int)
. However, if const
or
volatile
applies to the type to which a pointer
or reference parameter, then they are different.
// declares different functions void f(int*); void f(const int*); // also declares different functions void f(int&); void f(const int&);
void f(int x); void f(string* ps); f(0); // call f(int) // call f(int) if #define NULL 0, compiler error: type mis-match // if #define NULL ((void*) 0) or void * const NULL = 0; f(NULL); // call f(string*) if #define NULL ((void*) 0) or void * const NULL = 0; f(static_cast<string*>(NULL));One way to eliminate this is define NULL as object that acts as implicit conversion operator for every possible pointer type:
const // NULL is constant object class NullClass { public: template<class T> operator T*() const { return 0; } template<class C, class T> // handle pointer to member operator T C::*() const { return 0; } private: // prevent taking address as it should not act as pointer void operator&() const; } NULL;
However, don't overloading on different pointer types. NULL is generic to all type of pointers so they are ambiguous.
Overloading gives the appearance of permitting multiple occurrences of the same function name with different parameter lists. This is a lexical convenience that holds at the program source level. However, most link editors resolve external references lexically. If the link editor sees two or more instances of the name, it cannot analyze the types to distinguish between entities (by this point in the compilation, the type information is usually lost). Rather, the link editor flags as multiply defined and quits. To handle this problem, each function name with its associated parameter list is encoded as a unique internal name. They are vary across implementations. Because this encoding helps the link phase differentiate overloaded functions, it is called type-safe linkage.
This special encoding is not applied to functions declared
with the extern "C"
linkage directive. This
is why only one function in a set of overloaded functions
can be declared extern "C"
: two
extern "C"
functions with different parameter
lists are seen as the same function by the link editors.
C++ allows to specify default parameter for functions. Default value can either be specified in function declaration or definition, but not in both. Specifying default value in function prototype in header file make it visible once included, so it is recommended.
returntype Function1(void); returntype Function1(int); Function1(); // calls Function1(void) Function1(10); // calls Function1(int) returntype Function2(int parameter=0); Function2(); // calls Function2(0); Function2(10); // calls Function2(10);Use default parameter when
Function overload resolution is the process by which a function call is associated with one function in a set of overloaded functions.
Scope will affect function overload resolution as this determine the visibility of all candidate functions. Function declared in a nested scope hides rather than overloads a function having the same name in the outer scope. This is why no overloading across class scope, need to use using declartion or using directive to force overloading.
There are two categories of viable functions (ranking highest to lowest):
When a function expects a pass-by-value argument, conversion is
performed when the argument is an lvalue (object that can
address, its value can be fetched and, unless the object is
const
, its value can be modified).
void print(string); string color("purple"); print(color); // exact match: lvalue-to-rvalue conversion void print(list<int>&); list<int> li(20); print(li); // exact match: no lvalue-to-rvalue conversion
Qualification conversion affects only pointers. It is a
conversion that adds const
or
volatile
qualifiers (or both) to the type to which
a pointer points.
void print(const int*); int i = 1; int *pi = &i; print(pi); // exact match: qualification conversion void print(int *const); print(pi); // exact match: no qualification conversion
Exact match in which only an lvalue transformation (Lvalue-to-rvalue, Array-to-pointer, Function-to-pointer) is needed is ranked as better than an exact match requiring a qualification conversion.
char
, unsigned char
, or
short
to int
unsigned short
to int
is
enough, else unsigned int
float
to double
bool
to int
int
,
unsigned int
, long
or
unsigned long
. So, be aware of two enumeration
types may behave quite differently during function overload
resolution depending on the value of their enumeration
constants, which determines the type to which they promote.
void*
. If there is more than one pointer type,
conversion from value 0 to pointer is ambiguous. Only pointer
to data types can be converted to the type void*
implicitly, not pointer to function.
bool
.
ClassName::operator const char*()
convert
the class to const char*
.
Class object conversion because of inheritance is considered standard conversion:
A conversion sequence is used to convert an argument to the type of a viable function parameter. The rank of a conversion sequence is the rank of the worst conversion that makes up the sequence. In standard conversion sequence, the order is lvalue transformation, promotion or standard conversion followed by qualifications conversion. In user-defined conversion sequence, class member conversion function define the order.
The principle to find best viable function (or best match function) is not to do a conversion is better than any conversion. If no best viable function, then the function call is ambiguous; that is, the call does not match any viable function better than any other. Explicit casts can be used to break the ambiguity and force a call to resolve to a particular viable function.
void manip(vector<int>&);void manip(const vector<int>&); vector<int> f(); extern vector<int> vec; manip(vec); // calls manip(vector<int>&) manip(f()); // calls manip(const vector<int>&)
The rank of both conversion sequence is exact match. But the
reference initialization to call second one need additional
const
qualification, the first one is considered
the best viable function in the first call. In the second
call, the first one is not viable function because the argument
is a temporary that holds the return value of the function call,
it is an rvalue that cannot be used to initialize the non-const
reference parameter.
returntype Function1(int parameter); returntype Function1(double parameter1, double parameter2=0.0); Function1(1.0); // calls Function1(double,double)Since conversion to base class is standard conversion, it is more viable than conversion function (user-defined conversion). Conversion to base class that is less removed from the derived class is considered better than conversion to base class that is further. Similar rule also applies to pointers.
class A { // ... }; class B : public A { // ... }; class C : public B { // ... }; // usage in application returntype Function1(A* pa); returntype Function1(B* pb); C c; Function1(&c); // calls Function1(B*)
Multiple inheritance may cause two standard conversions from a derived class type to different base class types to be equally good if both base classes are equally removed from the derived class type. Explicit cast needed to avoid compilation error. Be aware also there is no implicit conversion from a base class type to a derived class type.
Inline function definition must be visible for the compiler to be able to inline a function at the point of the call. Unlike non-inline function, inline function must be defined in every text file in which the inline function is called. The definitions of the inline function that appear in different files must also be the same. So, place the definition of the inline function in a header file and include this header file in every text file in which the inline function is called.
If the code use function pointer to take the address of an inline function, compilers have to generate function body. In particular, compilers sometimes generate out-of-line copies of constructors and destructors so that they can get pointers to those functions for use in constructing and destructing arrays of objects of a class.
Index