Como obter sobrecarga de funções em C?


240

Existe alguma maneira de obter sobrecarga de função em C? Eu estou olhando para funções simples para serem sobrecarregadas como

foo (int a)  
foo (char b)  
foo (float c , int d)

Eu acho que não há caminho direto; Estou procurando soluções alternativas, se houver alguma.


6
Por que você quer fazer isso? C não possui habilidades polimórficas. Então foo (tipo aleatório) é impossível. Apenas certifique funcs reais foo_i, foo_ch, foo_d, etc.
jmucchiello

4
Você pode seguir o caminho errado usando ponteiros nulos e digitando IDs.
alk

11
Acho que devo chamar a atenção para o fato de que a resposta a essa pergunta mudou desde que foi originalmente solicitada , com o novo padrão C.
Leushenko 7/09/14

Respostas:


127

Existem poucas possibilidades:

  1. funções de estilo printf (digite como argumento)
  2. funções de estilo opengl (digite o nome da função)
  3. subconjunto c de c ++ (se você pode usar um compilador c ++)

1
você pode explicar ou fornecer links para funções no estilo opengl?
FL4SOF 26/01/09

1
@Lazer: Aqui está uma implementação simples da função printf-like.
Alexey Frunze

12
Não. Printf não está sobrecarregando a função. usa vararg !!! E C não suporta sobrecarga de funções.
Hqt 29/07/12

52
@hqt A resposta nunca menciona a palavra sobrecarga.
Kyrias

1
@kyrias Se a resposta não é sobre a sobrecarga é na pergunta errada
Michael Mrozek

233

Sim!

Desde que essa pergunta foi feita, o padrão C (sem extensões) ganhou efetivamente suporte para sobrecarga de funções (não operadores), graças à adição da _Genericpalavra - chave em C11. (suportado no GCC desde a versão 4.9)

(A sobrecarga não é realmente "incorporada" da maneira mostrada na pergunta, mas é muito fácil implementar algo que funciona dessa maneira.)

_Genericé um operador de tempo de compilação na mesma família que sizeofe _Alignof. É descrito na seção 6.5.1.1 padrão. Ele aceita dois parâmetros principais: uma expressão (que não será avaliada em tempo de execução) e uma lista de associação de tipo / expressão que se parece um pouco com um switchbloco. _Genericobtém o tipo geral da expressão e, em seguida, "alterna" para selecionar a expressão do resultado final na lista para seu tipo:

_Generic(1, float: 2.0,
            char *: "2",
            int: 2,
            default: get_two_object());

A expressão acima é avaliada como 2- o tipo da expressão de controle e int, portanto, escolhe a expressão associada intcomo o valor. Nada disso permanece em tempo de execução. (A defaultcláusula é opcional: se você a deixar desativada e o tipo não corresponder, isso causará um erro de compilação.)

A maneira como isso é útil para a sobrecarga de funções é que ela pode ser inserida pelo pré-processador C e escolher uma expressão de resultado com base no tipo de argumentos transmitidos para a macro de controle. Então (exemplo do padrão C):

#define cbrt(X) _Generic((X),                \
                         long double: cbrtl, \
                         default: cbrt,      \
                         float: cbrtf        \
                         )(X)

Essa macro implementa uma cbrtoperação sobrecarregada , despachando o tipo de argumento para a macro, escolhendo uma função de implementação apropriada e passando o argumento da macro original para essa função.

Então, para implementar seu exemplo original, poderíamos fazer o seguinte:

foo_int (int a)  
foo_char (char b)  
foo_float_int (float c , int d)

#define foo(_1, ...) _Generic((_1),                                  \
                              int: foo_int,                          \
                              char: foo_char,                        \
                              float: _Generic((FIRST(__VA_ARGS__,)), \
                                     int: foo_float_int))(_1, __VA_ARGS__)
#define FIRST(A, ...) A

Nesse caso, poderíamos ter usado uma default:associação para o terceiro caso, mas isso não demonstra como estender o princípio a vários argumentos. O resultado final é que você pode usar foo(...)seu código sem se preocupar (muito [1]) com o tipo de argumento.


Para situações mais complicadas, por exemplo, funções que sobrecarregam um número maior de argumentos ou números variáveis, você pode usar macros de utilitário para gerar automaticamente estruturas de despacho estáticas:

void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }

#define print(...) OVERLOAD(print, (__VA_ARGS__), \
    (print_ii, (int, int)), \
    (print_di, (double, int)), \
    (print_iii, (int, int, int)) \
)

#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"

int main(void) {
    print(44, 47);   // prints "int, int"
    print(4.4, 47);  // prints "double, int"
    print(1, 2, 3);  // prints "int, int, int"
    print("");       // prints "unknown arguments"
}

( implementação aqui ) Portanto, com algum esforço, você pode reduzir a quantidade de clichê para parecer uma linguagem com suporte nativo para sobrecarga.

Como um aparte, já era possível sobrecarregar o número de argumentos (não o tipo) em C99.


[1] observe que a maneira como C avalia os tipos pode enganá-lo. Isso escolherá foo_intse você tentar passar um literal de caractere, por exemplo, e precisar mexer um pouco se quiser que suas sobrecargas suportem literais de string. Ainda bem legal no geral.


Com base no seu exemplo, parece que a única coisa que está sendo sobrecarregada é funcionar como macros. Deixe-me ver se entendi corretamente: se você deseja sobrecarregar funções, você apenas usaria o pré-processador para desviar a chamada de função com base nos tipos de dados passados, certo?
Nick

Infelizmente, sempre que o C11 começa a entender, presumo que o MISRA não adotará esse recurso pelos mesmos motivos que proíbem listas de argumentos variáveis. Eu tento manter MISRA bem perto do meu mundo.
Nick

9
@ Nick isso é tudo sobrecarga é. Ele é manipulado implicitamente em outros idiomas (por exemplo, você não pode "obter um ponteiro para uma função sobrecarregada" em qualquer idioma, porque sobrecarregar implica vários corpos). Observe que isso não pode ser feito apenas pelo pré-processador, requer algum tipo de despacho; o pré-processador apenas muda a aparência.
Leushenko 5/02

1
Como alguém que é bastante familiarizado com C99 e quer aprender como fazer isso, isso parece muito complicado, mesmo para C.
Tyler Crompton

5
@TylerCrompton É avaliado em tempo de compilação.
JAB

75

Como já foi dito, a sobrecarga no sentido em que você quer dizer não é suportada por C. Um idioma comum para resolver o problema é fazer com que a função aceite uma união marcada . Isso é implementado por um structparâmetro, em que o structpróprio consiste em algum tipo de indicador de tipo, como um enum, e um uniondos diferentes tipos de valores. Exemplo:

#include <stdio.h>

typedef enum {
    T_INT,
    T_FLOAT,
    T_CHAR,
} my_type;

typedef struct {
    my_type type;
    union {
        int a; 
        float b; 
        char c;
    } my_union;
} my_struct;

void set_overload (my_struct *whatever) 
{
    switch (whatever->type) 
    {
        case T_INT:
            whatever->my_union.a = 1;
            break;
        case T_FLOAT:
            whatever->my_union.b = 2.0;
            break;
        case T_CHAR:
            whatever->my_union.c = '3';
    }
}

void printf_overload (my_struct *whatever) {
    switch (whatever->type) 
    {
        case T_INT:
            printf("%d\n", whatever->my_union.a);
            break;
        case T_FLOAT:
            printf("%f\n", whatever->my_union.b);
            break;
        case T_CHAR:
            printf("%c\n", whatever->my_union.c);
            break;
    }

}

int main (int argc, char* argv[])
{
    my_struct s;

    s.type=T_INT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_FLOAT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_CHAR;
    set_overload(&s);
    printf_overload(&s); 
}

22
Por que você não apenas fazer todas as whatevers em funções separadas ( set_int, set_float, etc). Em seguida, "marcar o tipo" se torna "adicione o nome do tipo ao nome da função". A versão nesta resposta envolve mais digitação, mais custo de tempo de execução, mais chances de erros que não serão detectados no momento da compilação ... Não vejo nenhuma vantagem em fazer as coisas dessa maneira! 16 votos positivos ?!
Ben

20
Ben, esta resposta é votada porque responde à pergunta, em vez de apenas dizer "não faça isso". Você está certo de que é mais idiomático em C usar funções separadas, mas se alguém quiser polimorfismo em C, essa é uma boa maneira de fazê-lo. Além disso, esta resposta mostra como você implementaria o polimorfismo em tempo de execução em um compilador ou VM: marque o valor com um tipo e, em seguida, despache com base nisso. É, portanto, uma excelente resposta à pergunta original.
Nils von Barth

20

Aqui está o exemplo mais claro e conciso que encontrei demonstrando sobrecarga de função em C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int addi(int a, int b) {
    return a + b;
}

char *adds(char *a, char *b) {
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)

int main(void) {
    int a = 1, b = 2;
    printf("%d\n", add(a, b)); // 3

    char *c = "hello ", *d = "world";
    printf("%s\n", add(c, d)); // hello world

    return 0;
}

https://gist.github.com/barosl/e0af4a92b2b8cabd05a7


1
Eu acho que isso é uma brincadeira de stackoverflow.com/a/25026358/1240268 em espírito (mas com menos explicações).
Andy Hayden

1
Definitivamente, prefiro um único bloco contínuo de código completo e executável à fatia de fatiar e cortar em cubos que é # 1240268. Cada um com sua mania.
Jay Taylor

1
Prefiro respostas que expliquem o que estão fazendo e por que funcionam. Isso também não. "Melhor que eu já vi:" não é exposição.
Underscore_d

19

Se o seu compilador é o gcc e você não se importa de fazer atualizações manuais toda vez que adiciona uma nova sobrecarga, você pode fazer alguma macro mágica e obter o resultado desejado em termos de chamadas, não é tão bom escrever ... mas é possível

observe __builtin_types_compatible_p e use-o para definir uma macro que faça algo como

#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

mas sim desagradável, apenas não

EDIT: C1X estará recebendo suporte para expressões genéricas de tipo com a seguinte aparência:

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

13

Sim, mais ou menos.

Aqui você vai por exemplo:

void printA(int a){
printf("Hello world from printA : %d\n",a);
}

void printB(const char *buff){
printf("Hello world from printB : %s\n",buff);
}

#define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 
#define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
#define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__) 
#define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) 
#define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
#define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) 
#define print(x , args ...) \
CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
({ \
if (__builtin_types_compatible_p (typeof (x), int)) \
printA(x, ##args); \
else \
printB (x,##args); \
})

int main(int argc, char** argv) {
    int a=0;
    print(a);
    print("hello");
    return (EXIT_SUCCESS);
}

Ele produzirá 0 e Olá. De printA e printB.


2
int main (int argc, char ** argv) {int a = 0; impressão (a); print ("olá"); return (EXIT_SUCCESS); } Saída será 0 e Olá .. de printa e printB ...
Capitão Barbossa

1
__builtin_types_compatible_p, esse compilador do GCC não é específico?
Sogartar 22/03

11

A abordagem a seguir é semelhante à de a2800276 , mas com alguma macro mágica C99 adicionada:

// we need `size_t`
#include <stddef.h>

// argument types to accept
enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };

// a structure to hold an argument
struct sum_arg
{
    enum sum_arg_types type;
    union
    {
        long as_long;
        unsigned long as_ulong;
        double as_double;
    } value;
};

// determine an array's size
#define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))

// this is how our function will be called
#define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))

// create an array of `struct sum_arg`
#define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })

// create initializers for the arguments
#define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
#define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
#define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }

// our polymorphic function
long double _sum(size_t count, struct sum_arg * args)
{
    long double value = 0;

    for(size_t i = 0; i < count; ++i)
    {
        switch(args[i].type)
        {
            case SUM_LONG:
            value += args[i].value.as_long;
            break;

            case SUM_ULONG:
            value += args[i].value.as_ulong;
            break;

            case SUM_DOUBLE:
            value += args[i].value.as_double;
            break;
        }
    }

    return value;
}

// let's see if it works

#include <stdio.h>

int main()
{
    unsigned long foo = -1;
    long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
    printf("%Le\n", value);
    return 0;
}

11

Isso pode não ajudar em nada, mas se você estiver usando o clang, poderá usar o atributo sobrecarregável - Isso funciona mesmo ao compilar como C

http://clang.llvm.org/docs/AttributeReference.html#overloadable

Cabeçalho

extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));

Implementação

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }

10

No sentido que você quer dizer - não, você não pode.

Você pode declarar uma va_argfunção como

void my_func(char* format, ...);

, mas você precisará passar algum tipo de informação sobre o número de variáveis ​​e seus tipos no primeiro argumento, como printf()faz.


6

Normalmente, uma verruga para indicar o tipo é anexada ou anexada ao nome. Você pode se safar das macros em alguns casos, mas isso depende do que você está tentando fazer. Não há polimorfismo em C, apenas coerção.

Operações genéricas simples podem ser feitas com macros:

#define max(x,y) ((x)>(y)?(x):(y))

Se o seu compilador suportar typeof , operações mais complicadas poderão ser colocadas na macro. Você pode ter o símbolo foo (x) para oferecer suporte à mesma operação em diferentes tipos, mas não pode variar o comportamento entre diferentes sobrecargas. Se você quiser funções reais em vez de macros, poderá colar o tipo no nome e usar uma segunda colagem para acessá-lo (eu não tentei).


você pode explicar um pouco mais sobre a abordagem baseada em macro.
FL4SOF 26/01/09

4

A resposta de Leushenko é muito legal - apenas: o fooexemplo não é compilado com o GCC, que falha foo(7), tropeçando na FIRSTmacro e na chamada de função real ( (_1, __VA_ARGS__)permanecendo com uma vírgula excedente. Além disso, estamos com problemas se queremos fornecer sobrecargas adicionais , como foo(double).

Então, decidi elaborar a resposta um pouco mais, inclusive para permitir uma sobrecarga vazia ( foo(void)- o que causou alguns problemas ...).

A idéia agora é: Defina mais de um genérico em diferentes macros e deixe selecionar o correto de acordo com o número de argumentos!

O número de argumentos é bastante fácil, com base nesta resposta :

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

Isso é legal, resolvemos um SELECT_1ou SELECT_2(ou mais argumentos, se você quiser / precisar deles), por isso precisamos de definições apropriadas:

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1),    \
        int: foo_int,                   \
        char: foo_char,                 \
        double: foo_double              \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

OK, já adicionei a sobrecarga vazia - no entanto, essa não é realmente coberta pelo padrão C, que não permite argumentos variados vazios, ou seja, confiamos nas extensões do compilador !

No começo, uma chamada de macro vazia ( foo()) ainda produz um token, mas um vazio. Portanto, a macro de contagem realmente retorna 1 em vez de 0, mesmo na chamada de macro vazia. Podemos "facilmente" eliminar esse problema, se colocarmos a vírgula após __VA_ARGS__ condicionalmente , dependendo da lista estar vazia ou não:

#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

Que parecia fácil, mas a COMMAmacro é muito pesado um; felizmente, o tópico já foi abordado em um blog de Jens Gustedt (obrigado, Jens). O truque básico é que as macros de função não são expandidas se não forem seguidas por parênteses, para obter mais explicações, dê uma olhada no blog de Jens ... Só precisamos modificar as macros um pouco para nossas necessidades (vou usar nomes mais curtos e menos argumentos por brevidade).

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,

E agora estamos bem ...

O código completo em um bloco:

/*
 * demo.c
 *
 *  Created on: 2017-09-14
 *      Author: sboehler
 */

#include <stdio.h>

void foo_void(void)
{
    puts("void");
}
void foo_int(int c)
{
    printf("int: %d\n", c);
}
void foo_char(char c)
{
    printf("char: %c\n", c);
}
void foo_double(double c)
{
    printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
    printf("double: %.2f, int: %d\n", c, d);
}

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)

#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y

#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
        int: foo_int,                \
        char: foo_char,              \
        double: foo_double           \
)
#define SELECT_2(_1, _2) _Generic((_1), \
        double: _Generic((_2),          \
                int: foo_double_int     \
        )                               \
)

#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N

#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)

#define SET_COMMA(...) ,

#define COMMA(...) SELECT_COMMA             \
(                                           \
        HAS_COMMA(__VA_ARGS__),             \
        HAS_COMMA(__VA_ARGS__ ()),          \
        HAS_COMMA(SET_COMMA __VA_ARGS__),   \
        HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)

#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3

#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,

int main(int argc, char** argv)
{
    foo();
    foo(7);
    foo(10.12);
    foo(12.10, 7);
    foo((char)'s');

    return 0;
}

1

Você não pode simplesmente usar C ++ e não usar todos os outros recursos de C ++, exceto este?

Se ainda não houver apenas C estrito, eu recomendaria funções variadas .


3
Não se um compilador C ++ não estiver disponível para o SO que ele está codificando.
Brian Brian

2
não apenas isso, mas ele pode querer um C ABI que não tenha nome mutilado.
Spudd86


-4

Espero que o código abaixo o ajude a entender a sobrecarga de funções

#include <stdio.h>
#include<stdarg.h>

int fun(int a, ...);
int main(int argc, char *argv[]){
   fun(1,10);
   fun(2,"cquestionbank");
   return 0;
}
int fun(int a, ...){
  va_list vl;
  va_start(vl,a);

  if(a==1)
      printf("%d",va_arg(vl,int));
   else
      printf("\n%s",va_arg(vl,char *));
}

2
Uma resposta deve explicar o que está fazendo e por que funciona. Se não, como pode ajudar alguém a entender alguma coisa?
Underscore_d

Não há sobrecarga aqui.
melpomene

va_end nunca foi chamado
user2262111
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.