Um nome, muitas versões
Muitas vezes você precisa da mesma operação para tipos de dados diferentes: imprimir um int, imprimir uma string, imprimir um double. Em algumas linguagens você inventaria printInt, printString, printDouble. C++ permite dar a todas o mesmo nome e as distingue pelos parâmetros. Isso é sobrecarga de funções.
A regra é simples: várias funções podem compartilhar um nome, desde que suas listas de parâmetros sejam diferentes - no número de parâmetros, em seus tipos ou em sua ordem. O compilador analisa os argumentos em cada ponto de chamada e escolhe para você a versão correspondente.
Três funções, um nome. Cada chamada cai na versão cujo tipo de parâmetro combina com o argumento. É isso que faz std::cout << x funcionar igualmente para ints, doubles e strings - operator<< é sobrecarregado muitas e muitas vezes.
O que conta como uma sobrecarga diferente
Uma sobrecarga é distinguida apenas pela sua lista de parâmetros. Você pode variar:
int area(int side); // 1 parâmetro
int area(int width, int height); // 2 parâmetros -> diferente
double area(double r); // tipo diferente -> diferente
void log(string msg, int level); // a ordem importa...
void log(int level, string msg); // ...então esta também é diferente
Cada uma dessas é uma sobrecarga legal e distinta. O compilador monta um conjunto de candidatas a partir de todas as funções chamadas area e depois faz a correspondência por número e tipo de argumentos.
O tipo de retorno sozinho não basta
Aqui está a armadilha que faz quase todo mundo tropeçar: você não pode sobrecarregar pelo tipo de retorno. O valor de retorno não tem papel na escolha da sobrecarga, porque o compilador decide qual função chamar a partir dos argumentos - antes mesmo de olhar o que é devolvido.
int convert(double x); // OK
double convert(double x); // ERRO: redefinição - só o tipo de retorno difere
Isso não compila. Se as listas de parâmetros são idênticas, as duas declarações são a mesma função no que diz respeito à sobrecarga, e você recebe um erro de redefinição. Para ramificar conforme o tipo do resultado, mude um parâmetro (ou use templates / uma conversão de tipo com static_cast no ponto de chamada).
Como a resolução de sobrecarga escolhe um vencedor
Quando você faz uma chamada, o compilador classifica todas as sobrecargas viáveis e escolhe a melhor correspondência. De modo geral, ele prefere, nesta ordem:
- Uma correspondência exata (sem conversão necessária).
- Uma promoção (ex.:
charoushort->int,float->double). - Uma conversão padrão (ex.:
int->double,double->int, ponteiro para a base).
Se exatamente uma sobrecarga é estritamente melhor que todas as outras, essa vence. Veja como uma correspondência exata supera uma conversão:
'A' é um char, mas uma promoção para int supera uma conversão para double, então ela chama a sobrecarga int. Essas regras de classificação são o motivo de a resolução de sobrecarga normalmente "fazer a coisa certa" - e, ocasionalmente, surpreender você.
A armadilha da ambiguidade
Se duas sobrecargas são igualmente boas - nenhuma estritamente melhor - o compilador se recusa a adivinhar e reporta uma chamada ambígua. O caso clássico são duas sobrecargas que cada uma precisa de uma conversão de mesmo nível:
void f(int x);
void f(double x);
f(0L); // ERRO: ambígua - long -> int e long -> double são conversões de mesmo nível
Nem int nem double são correspondência exata para long, e ambas as conversões ficam no mesmo nível, então a chamada é ambígua. Você tem duas soluções limpas:
Uma surpresa relacionada: passar um literal de string. void g(const string&) e void g(bool) ambas vão disputar g("hi"), e bool pode vencer, porque um const char* converte para bool (não nulo -> true) com menos passos do que construir um std::string. Se algum dia você vir um literal de string misteriosamente chamando sua sobrecarga bool, é por isso - adicione uma sobrecarga const char* ou const string& para que ela tome a correspondência exata.
Sobrecarga e argumentos padrão não combinam bem
Argumentos padrão não são um substituto para a sobrecarga, e combinar os dois cria ambiguidade. Cada um pode responder à mesma chamada, então o compilador não consegue escolher:
void connect(string host, int port = 8080); // pode ser chamada com 1 argumento
void connect(string host); // também chamável com 1 argumento
connect("localhost"); // ERRO: ambígua - ambas correspondem a um único argumento
Escolha uma abordagem por forma de chamada. Use argumentos padrão quando o comportamento é idêntico e você só quer parâmetros opcionais; use sobrecarga quando as listas de argumentos diferentes devem executar código genuinamente diferente. Misturá-los de modo que duas assinaturas colidam para a mesma quantidade de argumentos é um erro de ambiguidade garantido.
Mais uma distinção que vale a pena fixar: sobrecarga não é sobrescrita. A sobrecarga é resolvida em tempo de compilação entre funções no mesmo escopo com o mesmo nome mas parâmetros diferentes. A sobrescrita substitui uma função virtual em uma classe derivada em tempo de execução e exige a mesma assinatura - um tema para as funções virtuais mais adiante.
A seguir: Lambdas
A sobrecarga dá a um nome várias implementações tipadas escolhidas em tempo de compilação. Às vezes, porém, você não quer uma função nomeada de jeito nenhum - você precisa de uma pequena função descartável definida bem onde ela é usada, muitas vezes para passar a um algoritmo como sort. É exatamente isso que são as lambdas: funções anônimas que você pode escrever inline, com as quais captura variáveis ao redor e entrega em uma única expressão. A seguir veremos como escrevê-las e quando elas superam uma função nomeada completa.
Perguntas frequentes
O que é sobrecarga de funções em C++?
A sobrecarga de funções permite definir várias funções com o mesmo nome, desde que suas listas de parâmetros sejam diferentes (em número, tipo ou ordem). O compilador escolhe qual chamar com base nos argumentos que você passa, então print(42) e print("hi") podem chamar duas funções print diferentes.
Duas funções em C++ podem diferir apenas pelo tipo de retorno?
Não. As sobrecargas precisam diferir em sua lista de parâmetros. int f(int) e double f(int) causam um erro de compilação - o tipo de retorno não faz parte da assinatura usada na resolução de sobrecarga, porque o compilador escolhe a sobrecarga a partir dos argumentos no ponto de chamada, antes mesmo de o valor de retorno ser usado.
O que causa um erro de "chamada ambígua" com funções sobrecarregadas?
Acontece quando duas sobrecargas são igualmente boas e o compilador não consegue preferir uma. Um caso clássico é f(int) e f(double) chamadas com f(0L) (um long), em que ambas exigem uma conversão de mesmo nível. Corrija adicionando uma sobrecarga de correspondência exata ou convertendo o argumento para o tipo que você quer.