Funkcje można w C++ (ale nie w C) przeciążać. Oznacza to sytuację, gdy w tym samym zakresie są widoczne deklaracje/definicje kilku funkcji o tej samej nazwie. Oczywiście, aby na etapie kompilacji było wiadomo, o wywołanie której funkcji nam chodzi, wszystkie wersje funkcji muszą się wystarczająco różnić. Co to znaczy wystarczająco? Generalnie, muszą się różnić na tyle, aby można było jednoznacznie wybrać jedną z nich na podstawie wywołania.
Warunkiem koniecznym, choć niewystarczającym jest, aby funkcje o tej samej nazwie różniły się sygnaturą.
Tak więc na przykład
       int    fun(double x, int k =0);
       double fun(double z);
to dwie deklaracje różnych funkcji, ale o takiej samej sygnaturze,
a mianowicie o sygnaturze
fun(double).
I rzeczywiście, wywołanie
fun(1.5) byłoby najzupełniej legalnym i nie
wymagającym żadnej niejawnej konwersji wywołaniem zarówno
pierwszej, jak i drugiej z tych funkcji. Takie przeciążenie jest
zatem nielegalne.
Natomiast
       double fun(int);
       double fun(unsigned);
to deklaracje funkcji różniących się sygnaturą. Wywołanie
fun(15) jest wywołaniem pierwszej z nich,
bo '15'
jest literałem wartości typu
int
 i do przekształcenia tej
wartości do typu
unsigned
 potrzebna byłaby konwersja.
Zatem takie przeciążenie jest prawidłowe.
Z drugiej strony, różne sygnatury nie są jeszcze warunkiem dostatecznym na legalność przeciążenia. Widzimy to na przykładzie funkcji
       void fun(int i);
       void fun(int& i);
które mają różną sygnaturę, ale wywołanie
fun(k), gdzie
k
 jest typu
int, może być traktowane
jako wywołanie zarówno pierwszej, jak i drugiej z nich.
Zatem takie przeciążenie byłoby nieprawidłowe.
Podobnie nieprawidłowe byłoby przeciążenie
       void fun(int tab[]);
       void fun(int * p);
lub
       void fun(tab[3][3]);
       void fun(tab[5][3]);
bo pierwszy wymiar tablicy wielowymiarowej nie ma znaczenia do
określenia typu (jest i tak pomijany w tego rodzaju
deklaracji/definicji). Natomiast
       void fun(tab[3][3]);
       void fun(tab[3][5]);
prawidłowo deklaruje dwie przeciążone funkcje
fun,
gdyż tablice wielowymiarowe różniące się wymiarem innym niż
pierwszy są różnych typów i pomiędzy tymi typami nie ma
niejawnej konwersji.
Argument typu T może być użyty przy wywołaniu funkcji z parametrem typu T, const T i volatile T, więc funkcje przeciążone nie mogą się różnić tylko typem takiego parametru. Zatem
       int fun(int k);
       int fun(const int k);
byłoby nielegalne.
Natomiast typy parametrów T*, volatile T* i const T* (i analogicznie T&, volatile T& i const T&) są wystarczająco różne: patrząc na wywołanie funkcji kompilator może stwierdzić, czy użyta tam zmienna była ustalona lub ulotna czy nie; przeciążenie
       int fun(int& k);
       int fun(const int& k);
jest zatem prawidłowe.
Może się jednak zdarzyć, że to samo wywołanie funkcji pasuje do kilku jej przeciążonych wersji po ewentualnym dokonaniu dozwolonych konwersji. Jak rozstrzygnąć, która z tych funkcji będzie wywołana?
Proces poszukiwania takiej funkcji przebiega etapami i kończy się, gdy zostanie znalezione dopasowanie funkcji do wywołania. Tak więc sprawdzane są kolejno różne typy (stopnie) dopasowania:
Dopasowanie dokładne. Wszystkie typy argumentów są identyczne jak typy odpowiednich parametrów.
Dopasowanie po konwersji trywialnej. Do pełnego dopasowania wystarczy konwersja trywialna argumentu (ang. minor conversion). Konwersje trywialne to (T oznacza pewien typ, fun funkcję — o wskaźnikach funkcyjnych powiemy w rozdziale im poświęconym ):
| T | → | T& | 
| T& | → | T | 
| T[] | → | T* | 
| fun() | → | (*fun)() | 
| T | → | const T | 
| T | → | volatile T | 
| T* | → | const T* | 
| T* | → | volatile T* | 
Dopasowanie po promocji. Do uzyskania dopasowania wystarczą standardowe promocje całościowe lub zmiennopozycyjne — patrz rozdział o konwersjach standardowych — na przykład char → int.
Dopasowanie po innej konwersji niejawnej. Do uzyskania dopasowania wystarczą standardowe konwersje nie będące promocjami całościowymi lub zmiennopozycyjnymi — patrz rozdział o konwersjach standardowych — na przykład int → double lub odwrotnie.
Dopasowanie po konwersji zdefiniowanej przez użytkownika. Do uzyskania dopasowania potrzebne są konwersje zdefiniowane przez użytkownika — jak takie konwersje definiować, omówimy w rozdziale o konwersjach .
Dopasowanie do funkcji o zmiennej liczbie parametrów. Ostatnia, rozpaczliwa próba dopasowania może być podjęta, jeśli wywołanie może pasować do funkcji o nieustalonej liczbie parametrów.
Znajdowanie dopasowania to skomplikowany proces, którego wynik nie
zawsze jest intuicyjny. Rozpatrzmy program:
      1.  #include <iostream>
      2.  #include <string>
      3.  using namespace std;
      4.  
      5.  string fun1(   int) { return "\'int\'\n";    }
      6.  string fun1(  char) { return "\'char\'\n";   }
      7.  string fun1(double) { return "\'double\'\n"; }
      8.  
      9.  string fun2( short) { return "\'short\'\n";  }
     10.  string fun2(double) { return "\'double\'\n"; }
     11.  
     12.  int main() {
     13.      int    kin =   0;
     14.      char   kch = '\0';
     15.      float  kfl =   0;
     16.      double kdo =   0;
     17.  
     18.      cout << "fun1(   int) -> " << fun1(kin);
     19.      cout << "fun1(  char) -> " << fun1(kch);
     20.      cout << "fun1( float) -> " << fun1(kfl);  ➊
     21.      cout << "fun1(double) -> " << fun1(kdo);
     22.  
     23.      cout << "fun2( float) -> " << fun2(kfl);  ➋
     24.    //cout << "fun2(  char) -> " << fun2(kch);
     25.    //cout << "fun2(   int) -> " << fun2(kin);
     26.  }
Definiujemy trzy funkcje o tej samej nazwie fun1. Funkcje zwracają identyfikujący je napis. Wywołujemy je w funkcji main z argumentami różnych typów. Z wydruku
    fun1(   int) -> 'int'
    fun1(  char) -> 'char'
    fun1( float) -> 'double'
    fun1(double) -> 'double'
    fun2( float) -> 'double'
widzimy, że zawsze wywołana jest ta funkcja, której parametr
dokładnie pasuje do typu argumentu. Wyjątkiem jest wywołanie
z argumentem typu
float
 z linii ➊ — tu jednak nie dziwi nas,
że wybrana została funkcja z parametrem typu
double:
wystarczyła tu jedna standardowa promocja zmiennopozycyjna (konwersja
float
 
→
int
 też jest standardową konwersją,
ale nie standardową promocją).
Ciekawsze są natomiast wywołania funkcji fun2. Istnieją jej dwie wersje: z argumentem typu short i double. Nie ma kłopotów z wywołaniem funkcji z argumentem typu float (➋) — konwersja float → double jest standardową promocją. Ale zakomentowane wywołanie fun2(kch), gdzie kch jest typu char jest błędne! Argument jest tu typu char, więc wydawałoby się, że ma „bliżej” do typu short niż do typu double. Standardową promocją całościową jest jednak promocja char → int; promocja char → short nią nie jest i wobec tego jest uznana za „dopasowanie po innej konwersji niejawnej", a więc takie samo jak char → double. Zatem kompilator zgłosi błąd uznając obie wersje funkcji fun2 za tak samo dobre (albo tak samo złe).
Podobnie jest dla wykomentowanego wywołania tej samej funkcji z argumentem typu int (ostatnia linia). Wydaje się, że lepsza jest promocja int → double, będąca konwersją rozszerzającą, niż przekształcenie int → short, które jest konwersją zawężającą, a więc potencjalnie „gubiącą” informację. A jednak są one tak samo dobre/złe: ponieważ konwersja pierwsza jest, co prawda, promocją, ale nie całościową, obie znowu wpadają do tej samej kategorii „dopasowanie po innej konwersji niejawnej".
Oczywiście stosowanie takich nieczytelnych przeciążeń może prowadzić do trudno wykrywalnych błędów i generalnie powinniśmy ich unikać.
T.R. Werner, 21 lutego 2016; 20:17