Function

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.


  1. Passing function to another function for executing, use pointer to function.
  2. Switch among different functions which have same return type and parameter type, such as menu, using array of pointers to function.
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
};

An ellipsis is part of the function type. Two functions that have the same parameter list, except that one function has an additional ellipsis at the end of its parameter list, have different function types. Pointers to such functions will have different types.
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&);

Pointer to function as function's return value
int (*rpf(int))(int*, int); // return int (*)(int*, int);

// using typedef
typedef int (*FunctionPointer)(int*, int);
FunctionPointer rpf(int);

Function name and parameter types are used for identification. Overloading cannot be based on the return type because it is allowed to ignore returned value. Use overloaded functions instead of macro #define Function(parameter) ... because

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&);

Avoid overloading on a pointer and a numerical type because
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 Use function overloading in all other cases.


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):
  1. Exact match, or minor conversions
    1. Lvalue-to-rvalue conversion

      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
      
    2. Array-to-pointer conversion
    3. Function-to-pointer (pointer to function) conversion
    4. Qualification 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.

  2. Match with a type conversion (ranking highest to lowest)
    1. promotion (safe and no lost in conversion)
      1. char, unsigned char, or short to int
      2. unsigned short to int is enough, else unsigned int
      3. float to double
      4. bool to int
      5. enumeration constant to the first of the integral type that can represent all the values of the enumeration constants: 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.

    2. standard conversion
      1. integral conversions: the conversions from any integral type or enumeration to any other integral type (excluding the conversions that were listed as promotions earlier).
      2. floating point conversions: the conversions from any floating point type to any other floating point type (excluding the conversions that were listed as promotions earlier).
      3. floating-integral conversions: the conversions from any floating point type to any integral type or from any integral type to any floating point type.
      4. pointer conversions: the conversion of the integral value 0 (but not enumeration) to a pointer type and the conversion of a pointer of any type to the type 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.
      5. bool conversions: the conversions from any integral type, floating point type, enumeration type, or pointer type to the type bool.
    3. user-defined conversion, performed by a conversion function, a member function that allows a class to define its own set of "standard" conversions, for example ClassName::operator const char*() convert the class to const char*.

    Class object conversion because of inheritance is considered standard conversion:

    • Converting an argument of a derived class type to a parameter of any of its base class types.
    • Converting a pointer to a derived class type to a pointer to any of its base class types.
    • Initializing a reference to a base class type with an lvalue of a derived class type.

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.

There is not preference to choose function without default parameter against function with default 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 substitutes each occurrence of the function call with the text of the function body. So it can reduce the overhead associated with a function call. The disadvantages are:

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