How to achieve function overloading in C?

前端 未结 14 2524
清歌不尽
清歌不尽 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 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 
    
    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;
    }
    

提交回复
热议问题