How to achieve function overloading in C?

前端 未结 14 2300
清歌不尽
清歌不尽 2020-11-22 03:16

Is there any way to achieve function overloading in C? I am looking at simple functions to be overloaded like

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


        
相关标签:
14条回答
  • 2020-11-22 03:57

    I hope the below code will help you to understand function overloading

    #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 *));
    }
    
    0 讨论(0)
  • 2020-11-22 03:58

    If your compiler is gcc and you don't mind doing hand updates every time you add a new overload you can do some macro magic and get the result you want in terms of callers, it's not as nice to write... but it's possible

    look at __builtin_types_compatible_p, then use it to define a macro that does something like

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

    but yea nasty, just don't

    EDIT: C1X will be getting support for type generic expressions they look like this:

    #define cbrt(X) _Generic((X), long double: cbrtl, \
                                  default: cbrt, \
                                  float: cbrtf)(X)
    
    0 讨论(0)
  • 2020-11-22 04:03

    Yes!

    In the time since this question was asked, standard C (no extensions) has effectively gained support for function overloading (not operators), thanks to the addition of the _Generic keyword in C11. (supported in GCC since version 4.9)

    (Overloading isn't truly "built-in" in the fashion shown in the question, but it's dead easy to implement something that works like that.)

    _Generic is a compile-time operator in the same family as sizeof and _Alignof. It is described in standard section 6.5.1.1. It accepts two main parameters: an expression (which will not be evaluated at runtime), and a type/expression association list that looks a bit like a switch block. _Generic gets the overall type of the expression and then "switches" on it to select the end result expression in the list for its type:

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

    The above expression evaluates to 2 - the type of the controlling expression is int, so it chooses the expression associated with int as the value. Nothing of this remains at runtime. (The default clause is optional: if you leave it off and the type doesn't match, it will cause a compilation error.)

    The way this is useful for function overloading is that it can be inserted by the C preprocessor and choose a result expression based on the type of the arguments passed to the controlling macro. So (example from the C standard):

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

    This macro implements an overloaded cbrt operation, by dispatching on the type of the argument to the macro, choosing an appropriate implementation function, and then passing the original macro argument to that function.

    So to implement your original example, we could do this:

    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
    

    In this case we could have used a default: association for the third case, but that doesn't demonstrate how to extend the principle to multiple arguments. The end result is that you can use foo(...) in your code without worrying (much[1]) about the type of its arguments.


    For more complicated situations, e.g. functions overloading larger numbers of arguments, or varying numbers, you can use utility macros to automatically generate static dispatch structures:

    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"
    }
    

    (implementation here) So with some effort, you can reduce the amount of boilerplate to looking pretty much like a language with native support for overloading.

    As an aside, it was already possible to overload on the number of arguments (not the type) in C99.


    [1] note that the way C evaluates types might trip you up though. This will choose foo_int if you try to pass it a character literal, for instance, and you need to mess about a bit if you want your overloads to support string literals. Still overall pretty cool though.

    0 讨论(0)
  • 2020-11-22 04:05

    As already stated, overloading in the sense that you mean isn't supported by C. A common idiom to solve the problem is making the function accept a tagged union. This is implemented by a struct parameter, where the struct itself consists of some sort of type indicator, such as an enum, and a union of the different types of values. Example:

    #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); 
    }
    
    0 讨论(0)
  • 2020-11-22 04:11

    Yes, sort of.

    Here you go by example :

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

    It will output 0 and hello .. from printA and printB.

    0 讨论(0)
  • 2020-11-22 04:11

    Leushenko's answer is really cool - solely: the foo example does not compile with GCC, which fails at foo(7), stumbling over the FIRST macro and the actual function call ((_1, __VA_ARGS__), remaining with a surplus comma. Additionally, we are in trouble if we want to provide additional overloads, such as foo(double).

    So I decided to elaborate the answer a little further, including to allow a void overload (foo(void) – which caused quite some trouble...).

    Idea now is: Define more than one generic in different macros and let select the correct one according to the number of arguments!

    Number of arguments is quite easy, based on this answer:

    #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
    

    That's nice, we resolve to either SELECT_1 or SELECT_2 (or more arguments, if you want/need them), so we simply need appropriate defines:

    #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, I added the void overload already – however, this one actually is not covered by the C standard, which does not allow empty variadic arguments, i. e. we then rely on compiler extensions!

    At very first, an empty macro call (foo()) still produces a token, but an empty one. So the counting macro actually returns 1 instead of 0 even on empty macro call. We can "easily" eliminate this problem, if we place the comma after __VA_ARGS__ conditionally, depending on the list being empty or not:

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

    That looked easy, but the COMMA macro is quite a heavy one; fortunately, the topic is already covered in a blog of Jens Gustedt (thanks, Jens). Basic trick is that function macros are not expanded if not followed by parentheses, for further explanations, have a look at Jens' blog... We just have to modify the macros a little to our needs (I'm going to use shorter names and less arguments for brevity).

    #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 ,
    

    And now we are fine...

    The complete code in one block:

    /*
     * 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;
    }
    
    0 讨论(0)
提交回复
热议问题