วิธีการทำให้ฟังก์ชั่นโอเวอร์โหลดใน C เป็นอย่างไร?


240

มีวิธีใดบ้างที่จะทำให้เกิดการโอเวอร์โหลดของฟังก์ชันใน C? ฉันกำลังมองหาฟังก์ชั่นง่าย ๆ ที่จะโหลดมากเกินไปเช่น

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

ฉันคิดว่าไม่มีทางตรงไปข้างหน้า; ฉันกำลังมองหาวิธีแก้ไขหากมีอยู่


6
ทำไมคุณต้องการทำเช่นนี้? C ไม่มีความสามารถ polymorphic ดังนั้น foo (แบบสุ่ม) จึงเป็นไปไม่ได้ เพียงแค่ทำให้ funcs จริง foo_i, foo_ch, foo_d ฯลฯ
jmucchiello

4
คุณสามารถไปในทางที่ชั่วร้ายโดยใช้ตัวชี้โมฆะและรหัสประเภท
alk

11
ฉันรู้สึกว่าฉันควรให้ความสนใจกับความจริงที่ว่าคำตอบของคำถามนี้เปลี่ยนไปตั้งแต่แรกถามด้วยมาตรฐาน C ใหม่
Leushenko

คำตอบ:


127

มีความเป็นไปได้น้อย:

  1. ฟังก์ชันสไตล์ printf (พิมพ์เป็นอาร์กิวเมนต์)
  2. ฟังก์ชั่นสไตล์ OpenGL (พิมพ์ชื่อฟังก์ชั่น)
  3. c เซ็ตย่อยของ c ++ (ถ้าคุณสามารถใช้คอมไพเลอร์ c ++)

1
คุณสามารถอธิบายหรือให้ลิงค์สำหรับฟังก์ชั่นสไตล์ OpenGL ได้หรือไม่?
FL4SOF

1
@Lazer: นี่คือการใช้ฟังก์ชั่นเหมือนกับ printf อย่างง่าย
Alexey Frunze

12
ไม่ printf ไม่ทำงานมากไป มันใช้ vararg !!! และ C ไม่รองรับ Function Overloading
hqt

52
@hqt คำตอบไม่ได้พูดถึงคำที่มากไป
kyrias

1
@kyrias หากคำตอบไม่ได้เกี่ยวกับการใช้งานมากเกินไปมันเป็นคำถามที่ผิด
Michael Mrozek

233

ใช่

ในช่วงเวลานับตั้งแต่มีการถามคำถามนี้มาตรฐาน C (ไม่มีส่วนขยาย) ได้รับการสนับสนุนอย่างมีประสิทธิภาพสำหรับการโอเวอร์โหลดฟังก์ชั่น (ไม่ใช่ตัวดำเนินการ) ขอบคุณการเพิ่ม_Genericคำหลักใน C11 (รองรับใน GCC ตั้งแต่รุ่น 4.9)

(การบรรทุกเกินพิกัดไม่ใช่ "ตัวใน" อย่างแท้จริงในรูปแบบที่แสดงในคำถาม แต่มันตายง่ายที่จะใช้สิ่งที่ได้ผลเช่นนั้น)

_Genericเป็นผู้ประกอบการรวบรวมเวลาในตระกูลเดียวกับและsizeof _Alignofอธิบายไว้ในส่วนมาตรฐาน 6.5.1.1 ยอมรับสองพารามิเตอร์หลัก: นิพจน์ (ซึ่งจะไม่ถูกประเมินที่รันไทม์) และรายการการเชื่อมโยงประเภท / นิพจน์ที่มีลักษณะคล้ายswitchบล็อก _Genericรับประเภทโดยรวมของการแสดงออกแล้ว "สลับ" เพื่อเลือกการแสดงออกผลลัพธ์สุดท้ายในรายการประเภท:

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

นิพจน์ด้านบนประเมินว่า2- ประเภทของนิพจน์ควบคุมคือintดังนั้นมันจึงเลือกนิพจน์ที่สัมพันธ์กับintเป็นค่า ไม่มีสิ่งนี้เหลืออยู่ในรันไทม์ (ส่วนdefaultคำสั่งเป็นทางเลือก: หากคุณปล่อยทิ้งไว้และประเภทไม่ตรงกันจะทำให้เกิดข้อผิดพลาดในการรวบรวม)

วิธีนี้มีประโยชน์สำหรับการโอเวอร์โหลดฟังก์ชั่นคือสามารถแทรกโดยตัวประมวลผลล่วงหน้า C และเลือกนิพจน์ผลลัพธ์ตามชนิดของอาร์กิวเมนต์ที่ส่งผ่านไปยังแมโครควบคุม ดังนั้น (ตัวอย่างจากมาตรฐาน C):

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

แมโครนี้ใช้การดำเนินการที่โอเวอร์โหลดcbrtโดยการส่งประเภทของอาร์กิวเมนต์ไปยังแมโครเลือกฟังก์ชันการนำไปปฏิบัติที่เหมาะสมจากนั้นส่งผ่านอาร์กิวเมนต์แมโครเดิมไปยังฟังก์ชันนั้น

ดังนั้นในการใช้ตัวอย่างดั้งเดิมของคุณเราสามารถทำได้:

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

ในกรณีนี้เราอาจใช้การdefault:เชื่อมโยงสำหรับกรณีที่สาม แต่นั่นไม่ได้แสดงให้เห็นถึงวิธีการขยายหลักการไปสู่การขัดแย้งหลายประการ ผลลัพธ์ที่ได้คือคุณสามารถใช้foo(...)ในโค้ดของคุณโดยไม่ต้องกังวล (มาก [1]) เกี่ยวกับประเภทของอาร์กิวเมนต์


สำหรับสถานการณ์ที่ซับซ้อนมากขึ้นเช่นฟังก์ชั่นการโหลดจำนวนมากของการขัดแย้งหรือตัวเลขที่แตกต่างกันคุณสามารถใช้แมโครยูทิลิตี้เพื่อสร้างโครงสร้างการส่งแบบคงที่โดยอัตโนมัติ:

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

( การนำไปใช้ที่นี่ ) ดังนั้นด้วยความพยายามบางอย่างคุณสามารถลดจำนวนสำเร็จรูปให้ดูเหมือนภาษาที่สนับสนุนพื้นเมืองสำหรับการโอเวอร์โหลด

นอกเหนือจากนี้มันเป็นไปได้แล้วที่จะโอเวอร์โหลดในจำนวนอาร์กิวเมนต์ (ไม่ใช่ประเภท) ใน C99


[1] โปรดทราบว่าวิธีการประเมินประเภท C อาจทำให้คุณคุ้นเคย สิ่งนี้จะเลือกfoo_intถ้าคุณพยายามส่งผ่านตัวอักษรที่เป็นตัวอักษรและคุณจำเป็นต้องยุ่งเกี่ยวกับบิตหากคุณต้องการโอเวอร์โหลดของคุณเพื่อรองรับตัวอักษรสตริง โดยรวมยังคงค่อนข้างเย็น


จากตัวอย่างของคุณดูเหมือนว่าสิ่งเดียวที่มีการโอเวอร์โหลดคือฟังก์ชั่นเช่นมาโคร ให้ฉันดูว่าฉันเข้าใจถูกต้องหรือไม่: หากคุณต้องการโอเวอร์โหลดฟังก์ชั่นที่คุณเพิ่งใช้ preprocessor เพื่อโอนสายการทำงานตามประเภทข้อมูลที่ส่งผ่านใช่ไหม?
นิค

อนิจจาเมื่อใดก็ตามที่ C11 เริ่มจับฉันคิดว่า MISRA จะไม่ยอมรับคุณสมบัตินี้ด้วยเหตุผลเดียวกันกับที่พวกเขาห้ามรายการอาร์กิวเมนต์ตัวแปร ฉันพยายามติดโดย MISRA ในโลกของฉัน
นิค

9
@Nick นั่นคือทั้งหมดที่มากไปคือ มันเป็นเพียงการจัดการโดยนัยในภาษาอื่น ๆ (เช่นคุณไม่สามารถรับ "ตัวชี้ไปยังฟังก์ชั่นการโอเวอร์โหลด" ในภาษาใด ๆ ได้เพราะการใช้งานมากเกินไปหมายถึงหลาย ๆ ตัว) โปรดทราบว่าตัวประมวลผลล่วงหน้าไม่สามารถทำได้เพียงอย่างเดียว แต่ต้องใช้การแจกจ่ายบางชนิด ตัวประมวลผลล่วงหน้าเพียงเปลี่ยนลักษณะที่ปรากฏ
Leushenko

1
ในฐานะที่เป็นคนที่คุ้นเคยกับ C99 และต้องการเรียนรู้วิธีการทำสิ่งนี้ดูเหมือนว่าจะซับซ้อนเกินไปสำหรับ C.
Tyler Crompton

5
@TylerCrompton มันประเมินเวลารวบรวม
JAB

75

ตามที่ระบุไว้แล้วมากไปในแง่ที่ว่าคุณหมายถึงไม่ได้รับการสนับสนุนโดยซีสำนวนที่พบบ่อยในการแก้ปัญหาคือการทำให้ฟังก์ชั่นยอมรับสหภาพที่ติดแท็ก สิ่งนี้ถูกนำไปใช้โดยstructพารามิเตอร์โดยที่structตัวมันเองประกอบด้วยตัวบ่งชี้ประเภทบางประเภทเช่นenumและunionประเภทของค่าที่แตกต่างกัน ตัวอย่าง:

#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
ทำไมคุณไม่เพียงแค่ทำให้ทุกwhatevers เข้าไปในฟังก์ชั่นที่แยกต่างหาก ( set_int, set_floatฯลฯ ) จากนั้น "การติดแท็กด้วยประเภท" จะกลายเป็น "เพิ่มชื่อประเภทลงในชื่อฟังก์ชัน" รุ่นในคำตอบนี้เกี่ยวข้องกับการพิมพ์มากขึ้นค่าใช้จ่ายรันไทม์มากขึ้นมีโอกาสมากขึ้นที่ข้อผิดพลาดจะไม่ถูกรวบรวมในเวลารวบรวม ... ฉันไม่เห็นประโยชน์ใด ๆ เลยที่จะทำสิ่งนี้! โหวตขึ้น 16 คน!
Ben

20
เบ็นคำตอบนี้ยกขึ้นเพราะตอบคำถามแทนที่จะพูดว่า“ อย่าทำอย่างนั้น” คุณถูกต้องแล้วว่ามันเป็นสำนวนใน C มากกว่าที่จะใช้ฟังก์ชั่นแยกกัน แต่ถ้าใครต้องการ polymorphism ใน C นี่เป็นวิธีที่ดีที่จะทำ นอกจากนี้คำตอบนี้แสดงวิธีที่คุณจะใช้ polymorphism แบบรันไทม์ในคอมไพเลอร์หรือ VM: ติดแท็กค่าด้วยประเภทแล้วส่งตามนั้น มันจึงเป็นคำตอบที่ยอดเยี่ยมสำหรับคำถามต้นฉบับ
Nils von Barth

20

นี่คือตัวอย่างที่ชัดเจนและรัดกุมที่สุดที่ฉันได้พบแสดงให้เห็นถึงฟังก์ชั่นการโอเวอร์โหลดใน 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
ฉันคิดว่านี่เป็น dupe ของstackoverflow.com/a/25026358/1240268ด้วยจิตวิญญาณ (แต่มีคำอธิบายน้อยกว่า)
Andy Hayden

1
ฉันชอบโค้ดต่อเนื่องที่สมบูรณ์และรันได้ 1 บล็อกในการหั่นและหั่นลูกเต๋าที่ # 1240268 เพื่อให้แต่ละคนนั้นเอง
Jay Taylor

1
ฉันชอบคำตอบที่อธิบายสิ่งที่พวกเขาทำและทำไมพวกเขาถึงทำงาน สิ่งนี้ไม่ได้ "ดีที่สุดที่ฉันเคยเห็น:" ไม่ใช่การแสดงออก
underscore_d

19

หากคอมไพเลอร์ของคุณเป็น gcc และคุณไม่รังเกียจที่จะอัปเดตมือทุกครั้งที่คุณเพิ่มโอเวอร์โหลดใหม่คุณสามารถทำเวทย์มนตร์แมโครและรับผลลัพธ์ที่คุณต้องการในแง่ของผู้โทรมันไม่ดีที่จะเขียน ... แต่ก็เป็นไปได้

ดูที่ __builtin_types_compatible_p จากนั้นใช้เพื่อกำหนดแมโครที่ทำสิ่งที่ต้องการ

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

แต่ใช่น่ารังเกียจเพียงไม่

แก้ไข: C1X จะได้รับการสนับสนุนสำหรับนิพจน์ทั่วไปประเภทที่มีลักษณะดังนี้:

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

13

ใช่แล้ว

ที่นี่คุณไปตามตัวอย่าง:

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

มันจะออก 0 และสวัสดี .. จาก printA และ printB


2
int หลัก (int argc, ถ่าน ** argv) {int a = 0; พิมพ์ (ก); พิมพ์ ( "สวัสดี"); return (EXIT_SUCCESS); } จะส่งออก 0 และสวัสดี .. จาก printA และ printB ...
Captain Barbossa

1
__builtin_types_compatible_p นั่นคือคอมไพเลอร์ GCC เฉพาะหรือไม่
Sogartar

11

วิธีการต่อไปนี้คล้ายกับa2800276แต่เพิ่มมาโครเวทมนตร์ C99 บางส่วน:

// 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

สิ่งนี้อาจไม่ช่วยเลย แต่ถ้าคุณใช้เสียงดังกราวคุณสามารถใช้คุณลักษณะที่มากเกินไปได้ - ทำงานได้แม้เมื่อคอมไพล์เป็น C

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

หัวข้อ

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

การดำเนินงาน

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

10

ในแง่ที่คุณหมายถึง - ไม่คุณไม่สามารถ

คุณสามารถประกาศva_argฟังก์ชั่นเช่น

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

แต่คุณจะต้องผ่านบางชนิดของข้อมูลเกี่ยวกับจำนวนของตัวแปรและชนิดของพวกเขาในอาร์กิวเมนต์แรกที่ชอบ - printf()ไม่


6

โดยปกติหูดเพื่อระบุประเภทจะต่อท้ายหรือต่อท้ายชื่อ คุณสามารถหลบมาโครได้ในบางครั้ง แต่มันก็ขึ้นอยู่กับว่าคุณพยายามทำอะไร ไม่มีความแตกต่างใน C บังคับเท่านั้น

การดำเนินการทั่วไปอย่างง่ายสามารถทำได้ด้วยมาโคร:

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

หากสนับสนุนคอมไพเลอร์ของคุณtypeofการดำเนินงานที่มีความซับซ้อนมากขึ้นสามารถใส่ในแมโคร จากนั้นคุณสามารถมีสัญลักษณ์ foo (x) เพื่อสนับสนุนการทำงานประเภทต่าง ๆ ที่เหมือนกัน แต่คุณไม่สามารถเปลี่ยนแปลงพฤติกรรมระหว่างการโอเวอร์โหลดที่แตกต่างกันได้ หากคุณต้องการฟังก์ชั่นที่เกิดขึ้นจริงมากกว่ามาโครคุณอาจจะสามารถวางประเภทลงในชื่อและใช้การวางที่สองเพื่อเข้าถึง (ฉันยังไม่ได้ลอง)


คุณสามารถอธิบายเพิ่มเติมเล็กน้อยเกี่ยวกับวิธีการที่ใช้มาโคร
FL4SOF

4

คำตอบของ Leushenkoนั้นเจ๋งจริง ๆ - เพียงอย่างเดียว: fooตัวอย่างไม่ได้รวบรวม GCC ซึ่งล้มเหลวfoo(7)โดยสะดุดเหนือFIRSTแมโครและการเรียกใช้ฟังก์ชันจริง ( (_1, __VA_ARGS__)เหลืออยู่ด้วยเครื่องหมายจุลภาคส่วนเกินนอกจากนี้เรากำลังมีปัญหาหากเราต้องการให้มีการโอเวอร์โหลดเพิ่มเติม foo(double)เช่น

ดังนั้นฉันตัดสินใจที่จะอธิบายคำตอบให้ละเอียดมากขึ้นอีกเล็กน้อยรวมถึงอนุญาตให้มีโมฆะโอเวอร์โหลด ( foo(void)- ซึ่งทำให้เกิดปัญหาค่อนข้าง ... )

แนวคิดตอนนี้คือ: กำหนดมากกว่าหนึ่งตัวในแมโครที่แตกต่างกันและให้เลือกที่ถูกต้องตามจำนวนอาร์กิวเมนต์!

จำนวนข้อโต้แย้งค่อนข้างง่ายขึ้นอยู่กับคำตอบนี้ :

#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

เป็นเรื่องที่ดีเราแก้ไขเป็นอย่างใดอย่างหนึ่งSELECT_1หรือSELECT_2(หรือมากกว่าอาร์กิวเมนต์ถ้าคุณต้องการ / ต้องการพวกเขา) ดังนั้นเราจึงจำเป็นต้องกำหนดที่เหมาะสม:

#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     \
        )                               \
)

ตกลงฉันได้เพิ่มโมฆะการโหลดเกินแล้ว - อย่างไรก็ตามอันนี้จริง ๆ แล้วไม่ครอบคลุมโดยมาตรฐาน C ซึ่งไม่อนุญาตให้มีการขัดแย้ง Variadic ที่ว่างเปล่านั่นคือเราจะพึ่งพาส่วนขยายคอมไพเลอร์ !

ในตอนแรกการเรียกมาโครที่ว่างเปล่า ( foo()) ยังคงสร้างโทเค็น แต่จะว่างเปล่า ดังนั้นการนับมาโครจริง ๆ แล้วส่งกลับค่า 1 แทน 0 แม้ว่าจะเรียกแมโครว่างเปล่า เราสามารถ "กำจัดปัญหานี้ได้อย่างง่ายดาย" ถ้าเราวางเครื่องหมายจุลภาคหลังจาก__VA_ARGS__ เงื่อนไขขึ้นอยู่กับรายการว่างเปล่าหรือไม่:

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

ที่มองง่าย แต่COMMAแมโครค่อนข้างหนักหนึ่ง; โชคดีที่หัวข้อนี้ได้รับการกล่าวถึงในบล็อกของ Jens Gustedt (ขอบคุณ Jens) เคล็ดลับพื้นฐานคือฟังก์ชั่นมาโครจะไม่ขยายหากไม่ได้ตามด้วยวงเล็บสำหรับคำอธิบายเพิ่มเติมดูที่บล็อกของ Jens ... เราต้องแก้ไขมาโครเล็กน้อยตามความต้องการของเรา (ฉันจะใช้ชื่อที่สั้นกว่า และอาร์กิวเมนต์น้อยลงสำหรับความกะทัดรัด)

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

และตอนนี้เราสบายดี ...

รหัสที่สมบูรณ์ในหนึ่งบล็อก:

/*
 * 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

คุณไม่สามารถใช้ C ++ และไม่ใช้คุณสมบัติ C ++ อื่น ๆ ทั้งหมดยกเว้นคุณลักษณะนี้ได้หรือไม่

หากยังไม่เข้มงวด C ก็จะแนะนำฟังก์ชัน Variadicแทน


3
ไม่ใช่ถ้าคอมไพเลอร์ C ++ ไม่สามารถใช้ได้สำหรับระบบปฏิบัติการที่เขากำลังให้รหัส
ไบรอัน

2
ไม่เพียงแค่นั้น แต่เขาอาจต้องการ C ABI ที่ไม่มีชื่ออยู่ในนั้น
Spudd86

-3

ลองประกาศฟังก์ชั่นเหล่านี้ราวกับextern "C++"ว่าคอมไพเลอร์ของคุณรองรับสิ่งนี้http://msdn.microsoft.com/en-us/library/s6y4zxec(VS.80).aspx


3
สิ่งนี้อาจเปลี่ยนชื่อ mangling เพื่อให้ชื่อเฉพาะ (อาจไม่ใช่) แต่จะไม่ให้กฎการแก้ปัญหาโอเวอร์โหลด C ในทันที
Ben Voigt

-4

ฉันหวังว่ารหัสด้านล่างจะช่วยให้คุณเข้าใจการทำงานมากเกินไป

#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
คำตอบควรอธิบายสิ่งที่มันทำและทำไมมันทำงาน หากไม่เป็นเช่นนั้นจะช่วยให้ทุกคนเข้าใจอะไรได้อย่างไร
underscore_d

ไม่มีการบรรทุกเกินพิกัดที่นี่
melpomene

va_end ไม่เคยถูกเรียก
user2262111
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.