Tiêu chuẩn thay thế cho thủ thuật ## __ VA_ARGS__ của GCC?


151

Có một vấn đề nổi tiếng với các đối số trống cho các macro biến đổi trong C99.

thí dụ:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

Việc sử dụng BAR()ở trên thực sự không chính xác theo tiêu chuẩn C99, vì nó sẽ mở rộng sang:

printf("this breaks!",);

Lưu ý dấu phẩy - không khả thi.

Một số trình biên dịch (ví dụ: Visual Studio 2010) sẽ lặng lẽ loại bỏ dấu phẩy đó cho bạn. Các trình biên dịch khác (ví dụ: GCC) hỗ trợ đặt ##trước __VA_ARGS__, như vậy:

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

Nhưng có một cách tuân thủ tiêu chuẩn để có được hành vi này? Có lẽ sử dụng nhiều macro?

Ngay bây giờ, ##phiên bản có vẻ được hỗ trợ khá tốt (ít nhất là trên nền tảng của tôi), nhưng tôi thực sự muốn sử dụng một giải pháp tuân thủ tiêu chuẩn.

Pre-emptive: Tôi biết tôi chỉ có thể viết một hàm nhỏ. Tôi đang cố gắng làm điều này bằng cách sử dụng macro.

Chỉnh sửa : Đây là một ví dụ (mặc dù đơn giản) về lý do tại sao tôi muốn sử dụng BAR ():

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

Điều này tự động thêm một dòng mới vào các báo cáo ghi nhật ký BAR () của tôi, giả sử fmtluôn luôn là một chuỗi C được trích dẫn kép. Nó KHÔNG in dòng mới dưới dạng printf () riêng biệt, thuận lợi nếu việc ghi nhật ký được đệm dòng và đến từ nhiều nguồn không đồng bộ.


3
Tại sao sử dụng BARthay vì FOOở nơi đầu tiên?
GManNickG

@GMan: Tôi đã thêm một ví dụ ở cuối
jwd

5
@GMan: Đọc câu cuối cùng (:
jwd


2
@zwol phiên bản mới nhất được gửi tới WG14 trông như thế này , sử dụng cú pháp mới dựa trên __VA_OPT__từ khóa. Điều này đã được "thông qua" bởi C ++, vì vậy tôi hy vọng C sẽ làm theo. (không biết điều đó có nghĩa là nó được theo dõi nhanh vào C ++ 17 hay nếu nó được đặt cho C ++ 20)
Leushenko

Câu trả lời:


66

Có thể tránh việc sử dụng ,##__VA_ARGS__tiện ích mở rộng của GCC nếu bạn sẵn sàng chấp nhận một số giới hạn trên được mã hóa cứng đối với số lượng đối số bạn có thể chuyển sang macro biến đổi, như được mô tả trong câu trả lời của Richard Hansen cho câu hỏi này . Tuy nhiên, nếu bạn không muốn có bất kỳ giới hạn nào như vậy, theo hiểu biết tốt nhất của tôi, không thể chỉ sử dụng các tính năng tiền xử lý do C99 chỉ định; bạn phải sử dụng một số phần mở rộng cho ngôn ngữ. clang và icc đã áp dụng phần mở rộng GCC này, nhưng MSVC thì không.

Quay trở lại năm 2001, tôi đã viết ra phần mở rộng GCC để tiêu chuẩn hóa (và phần mở rộng có liên quan cho phép bạn sử dụng một tên khác với __VA_ARGS__tham số còn lại) trong tài liệu N976 , nhưng không nhận được phản hồi nào từ ủy ban; Tôi thậm chí không biết nếu có ai đọc nó. Năm 2016 nó đã được đề xuất một lần nữa vào năm N2023 và tôi khuyến khích bất cứ ai biết đề xuất đó sẽ cho chúng tôi biết như thế nào trong các bình luận.


2
Đánh giá khuyết tật của tôi để tìm giải pháp trên web và thiếu câu trả lời ở đây, tôi đoán bạn đã đúng):
jwd

2
n976 những gì bạn đang đề cập đến? Tôi đã tìm kiếm phần còn lại của các tài liệu của nhóm làm việc C để tìm phản hồi nhưng không bao giờ tìm thấy. Nó thậm chí không có trong chương trình nghị sự cho cuộc họp tiếp theo . Điểm nhấn duy nhất khác về chủ đề này là bình luận số 4 của Na Uy vào năm n868 từ trước khi C99 được phê chuẩn (một lần nữa không có thảo luận tiếp theo).
Richard Hansen

4
Vâng, cụ thể là nửa sau của điều đó. Có thể đã có cuộc thảo luận về comp.std.cnhưng tôi không thể tìm thấy bất kỳ ai trong Google Groups; nó chắc chắn không bao giờ có bất kỳ sự chú ý nào từ ủy ban thực tế (hoặc nếu có, không ai từng nói với tôi về điều đó).
zwol

1
Tôi sợ rằng tôi không có bằng chứng, tôi cũng không phải là người phù hợp để cố gắng nghĩ ra. Tôi đã viết một nửa bộ tiền xử lý của GCC, nhưng đó là hơn mười năm trước và tôi chưa bao giờ nghĩ đến thủ thuật đếm đối số dưới đây, ngay cả khi đó.
zwol

6
Phần mở rộng này hoạt động với trình biên dịch icc clang & intel, cũng như gcc.
ACyclic

112

Có một mẹo đếm đối số mà bạn có thể sử dụng.

Đây là một cách tuân thủ tiêu chuẩn để thực hiện BAR()ví dụ thứ hai trong câu hỏi của jwd:

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

Thủ thuật tương tự này được sử dụng để:

Giải trình

Chiến lược là tách __VA_ARGS__thành đối số đầu tiên và phần còn lại (nếu có). Điều này làm cho nó có thể chèn công cụ sau đối số đầu tiên nhưng trước đối số thứ hai (nếu có).

FIRST()

Macro này chỉ đơn giản là mở rộng cho đối số đầu tiên, loại bỏ phần còn lại.

Việc thực hiện rất đơn giản. Đối throwawaysố đảm bảo có FIRST_HELPER()được hai đối số, được yêu cầu vì ...cần ít nhất một đối số. Với một đối số, nó mở rộng như sau:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

Với hai hoặc nhiều hơn, nó mở rộng như sau:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

Macro này mở rộng ra mọi thứ trừ đối số đầu tiên (bao gồm cả dấu phẩy sau đối số đầu tiên, nếu có nhiều hơn một đối số).

Việc thực hiện vĩ mô này phức tạp hơn nhiều. Chiến lược chung là đếm số lượng đối số (một hoặc nhiều hơn một) và sau đó mở rộng thành một REST_HELPER_ONE()(nếu chỉ có một đối số được đưa ra) hoặc REST_HELPER_TWOORMORE()(nếu hai hoặc nhiều đối số được đưa ra). REST_HELPER_ONE()chỉ đơn giản là mở rộng thành không có gì - không có đối số nào sau lần đầu tiên, vì vậy các đối số còn lại là tập hợp trống. REST_HELPER_TWOORMORE()cũng đơn giản - nó mở rộng thành dấu phẩy theo sau bởi mọi thứ trừ đối số đầu tiên.

Các đối số được tính bằng cách sử dụng NUM()macro. Macro này mở rộng thành ONEnếu chỉ có một đối số được đưa ra, TWOORMOREnếu từ hai đến chín đối số được đưa ra và ngắt nếu 10 hoặc nhiều đối số được đưa ra (vì nó mở rộng sang đối số thứ 10).

Các NUM()vĩ mô sử dụng SELECT_10TH()vĩ mô để xác định số lượng các đối số. Như tên của nó ngụ ý, SELECT_10TH()chỉ cần mở rộng đến đối số thứ 10 của nó. Do dấu chấm lửng, SELECT_10TH()cần phải được thông qua ít nhất 11 đối số (tiêu chuẩn nói rằng phải có ít nhất một đối số cho dấu chấm lửng). Đây là lý do tại sao NUM()vượt qua throwawaynhư là đối số cuối cùng (không có nó, chuyển một đối số NUM()sẽ chỉ dẫn đến 10 đối số được truyền tới SELECT_10TH(), điều này sẽ vi phạm tiêu chuẩn).

Lựa chọn một trong hai REST_HELPER_ONE()hoặc REST_HELPER_TWOORMORE()được thực hiện bằng cách nối REST_HELPER_với mở rộng NUM(__VA_ARGS__)trong REST_HELPER2(). Lưu ý rằng mục đích của REST_HELPER()là để đảm bảo rằng nó NUM(__VA_ARGS__)được mở rộng hoàn toàn trước khi được nối với REST_HELPER_.

Mở rộng với một đối số như sau:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (trống)

Mở rộng với hai hoặc nhiều đối số diễn ra như sau:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

1
Lưu ý rằng điều này sẽ thất bại nếu bạn gọi BAR với 10 đối số trở lên và mặc dù việc mở rộng ra nhiều đối số tương đối dễ dàng hơn, nhưng nó sẽ luôn bị giới hạn trên về số lượng đối số có thể giải quyết
Chris Dodd

2
@ChrisDodd: Đúng. Thật không may, dường như không có cách nào để tránh giới hạn về số lượng đối số mà không phụ thuộc vào các tiện ích mở rộng dành riêng cho trình biên dịch. Ngoài ra, tôi không biết cách kiểm tra đáng tin cậy nếu có quá nhiều đối số (để có thể in thông báo lỗi trình biên dịch hữu ích, thay vì một lỗi lạ).
Richard Hansen

17

Không phải là một giải pháp chung, nhưng trong trường hợp printf, bạn có thể nối thêm một dòng mới như:

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

Tôi tin rằng nó bỏ qua mọi đối số bổ sung không được tham chiếu trong chuỗi định dạng. Vì vậy, bạn thậm chí có thể thoát khỏi:

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

Tôi không thể tin rằng C99 đã được phê duyệt mà không có cách tiêu chuẩn để làm điều này. AFAICT vấn đề tồn tại trong C ++ 11 cũng vậy.


vấn đề với số 0 thêm này là nó thực sự sẽ kết thúc trong mã nếu nó gọi hàm vararg. Kiểm tra giải pháp được cung cấp bởi Richard Hansen
Pavel P

@Pavel đúng về ví dụ thứ hai, nhưng cái đầu tiên hoạt động rất tốt. +1.
kirbyfan64sos

11

Có một cách để xử lý trường hợp cụ thể này bằng cách sử dụng một cái gì đó như Boost.Pre Processor . Bạn có thể sử dụng BOOST_PP_VARIADIC_SIZE để kiểm tra kích thước của danh sách đối số và sau đó mở rộng có điều kiện sang macro khác. Một thiếu sót của điều này là nó không thể phân biệt giữa đối số 0 và 1 và lý do cho điều này trở nên rõ ràng sau khi bạn xem xét các điều sau:

BOOST_PP_VARIADIC_SIZE()      // expands to 1
BOOST_PP_VARIADIC_SIZE(,)     // expands to 2
BOOST_PP_VARIADIC_SIZE(,,)    // expands to 3
BOOST_PP_VARIADIC_SIZE(a)     // expands to 1
BOOST_PP_VARIADIC_SIZE(a,)    // expands to 2
BOOST_PP_VARIADIC_SIZE(,b)    // expands to 2
BOOST_PP_VARIADIC_SIZE(a,b)   // expands to 2
BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

Danh sách đối số macro trống thực sự bao gồm một đối số xảy ra để trống.

Trong trường hợp này, chúng tôi rất may mắn vì macro mong muốn của bạn luôn có ít nhất 1 đối số, chúng tôi có thể triển khai nó dưới dạng hai macro "quá tải":

#define BAR_0(fmt) printf(fmt "\n")
#define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)

Và sau đó, một macro khác để chuyển đổi giữa chúng, chẳng hạn như:

#define BAR(...) \
    BOOST_PP_CAT(BAR_, BOOST_PP_GREATER(
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \
    /**/

hoặc là

#define BAR(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        BAR_1, BAR_0)(__VA_ARGS__) \
    /**/

Bất cứ điều gì bạn thấy dễ đọc hơn (tôi thích cái đầu tiên vì nó cung cấp cho bạn một hình thức chung để nạp chồng macro vào số lượng đối số).

Cũng có thể thực hiện điều này với một macro duy nhất bằng cách truy cập và thay đổi danh sách đối số biến, nhưng cách này ít đọc hơn và rất cụ thể cho vấn đề này:

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_COMMA_IF( \
        BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \
    BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

Ngoài ra, tại sao không có BOOST_PP_ARRAY_ENUM_TRAILING? Nó sẽ làm cho giải pháp này ít kinh khủng hơn nhiều.

Chỉnh sửa: Được rồi, đây là BOOST_PP_ARRAY_ENUM_TRAILING và một phiên bản sử dụng nó (đây hiện là giải pháp yêu thích của tôi):

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \
    BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \
    /**/

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

1
Rất vui được tìm hiểu về Boost.Pre Processor, +1. Lưu ý rằng BOOST_PP_VARIADIC_SIZE()sử dụng cùng một mẹo đếm đối số mà tôi đã ghi lại trong câu trả lời của mình và có cùng giới hạn (nó sẽ bị hỏng nếu bạn vượt qua nhiều số lượng đối số nhất định).
Richard Hansen

1
Đúng, tôi thấy rằng cách tiếp cận của bạn giống với cách sử dụng của Boost, nhưng giải pháp tăng cường được duy trì rất tốt và có nhiều tính năng thực sự hữu ích khác để sử dụng khi phát triển các macro phức tạp hơn. Các công cụ đệ quy đặc biệt thú vị (và được sử dụng đằng sau hậu trường theo cách tiếp cận cuối cùng sử dụng BOOST_PP_ARRAY_ENUM).
DRayX

1
Một câu trả lời Boost thực sự áp dụng cho thẻ c ! Hoan hô!
Justin

6

Một macro rất đơn giản tôi đang sử dụng để in gỡ lỗi:

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "nice!");
        return 0;
}

Cho dù có bao nhiêu đối số được truyền cho DBG, không có cảnh báo c99.

Thủ thuật là __DBG_INTthêm một thông số giả nên ...sẽ luôn có ít nhất một đối số và c99 được thỏa mãn.


5

Gần đây tôi gặp phải một vấn đề tương tự và tôi tin rằng có một giải pháp.

Ý tưởng chính là có một cách để viết một macro NUM_ARGSđể đếm số lượng đối số mà một macro biến đổi được đưa ra. Bạn có thể sử dụng một biến thể của NUM_ARGSđể xây dựng NUM_ARGS_CEILING2, điều này có thể cho bạn biết liệu macro biến đổi được đưa ra 1 đối số hay 2 đối số trở lên. Sau đó, bạn có thể viết Barmacro của mình để nó sử dụng NUM_ARGS_CEILING2CONCATgửi các đối số của nó tới một trong hai macro trợ giúp: một đối số mong đợi chính xác 1 đối số và một đối số khác mong đợi số lượng đối số thay đổi lớn hơn 1.

Đây là một ví dụ trong đó tôi sử dụng thủ thuật này để viết macro UNIMPLEMENTED, rất giống với BAR:

BƯỚC 1:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

BƯỚC 1.5:

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

Bước 2:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

BƯỚC 3:

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

Trường hợp CONCAT được thực hiện theo cách thông thường. Như một gợi ý nhanh, nếu những điều trên có vẻ khó hiểu: mục tiêu của CONCAT là mở rộng sang một "cuộc gọi" vĩ mô khác.

Lưu ý rằng bản thân NUM_ARGS không được sử dụng. Tôi chỉ bao gồm nó để minh họa các mẹo cơ bản ở đây. Xem blog P99 của Jens Gustyt để biết cách xử lý tốt.

Hai lưu ý:

  • NUM_ARGS bị giới hạn về số lượng đối số mà nó xử lý. Của tôi chỉ có thể xử lý tối đa 20, mặc dù số lượng là hoàn toàn tùy ý.

  • NUM_ARGS, như được hiển thị, có một cạm bẫy ở chỗ nó trả về 1 khi đưa ra 0 đối số. Điểm chính của nó là NUM_ARGS về mặt kỹ thuật đang đếm [dấu phẩy + 1] và không tranh luận. Trong trường hợp cụ thể này, nó thực sự hoạt động để lợi thế của chúng tôi. _UNIMPLEMENTED1 sẽ xử lý một mã thông báo trống tốt và nó giúp chúng ta không phải viết _UNIMPLEMENTED0. Gustyt cũng có cách giải quyết cho vấn đề đó, mặc dù tôi chưa sử dụng nó và tôi không chắc liệu nó có hoạt động cho những gì chúng tôi đang làm ở đây không.


+1 để đưa ra thủ thuật đếm đối số, -1 vì thực sự khó theo dõi
Richard Hansen

Các ý kiến ​​bạn đã thêm là một sự cải tiến, nhưng vẫn còn một số vấn đề: 1. Bạn thảo luận và xác định NUM_ARGSnhưng không sử dụng nó. 2. Mục đích của là UNIMPLEMENTEDgì? 3. Bạn không bao giờ giải quyết vấn đề ví dụ trong câu hỏi. 4. Đi qua việc mở rộng từng bước một sẽ minh họa cách thức hoạt động và giải thích vai trò của từng macro trợ giúp. 5. Thảo luận về 0 đối số là mất tập trung; OP đã hỏi về việc tuân thủ tiêu chuẩn và 0 đối số bị cấm (C99 6.10.3p4). 6. Bước 1.5? Tại sao không bước 2? 7. "Các bước" ngụ ý các hành động xảy ra tuần tự; Đây chỉ là mã.
Richard Hansen

8. Bạn liên kết đến toàn bộ blog, không phải bài viết liên quan. Tôi không thể tìm thấy bài viết mà bạn đang đề cập đến. 9. Đoạn cuối thật khó xử: Phương pháp này tối nghĩa; đó là lý do tại sao không ai khác đã đăng một giải pháp chính xác trước đó. Ngoài ra, nếu nó hoạt động và tuân thủ tiêu chuẩn, câu trả lời của Zack phải sai. 10. Bạn nên xác định CONCAT()- đừng cho rằng người đọc biết nó hoạt động như thế nào.
Richard Hansen

(Xin đừng diễn giải phản hồi này là một cuộc tấn công - Tôi thực sự muốn nâng cao câu trả lời của bạn nhưng không cảm thấy thoải mái khi làm điều đó trừ khi nó dễ hiểu hơn. Nếu bạn có thể cải thiện sự rõ ràng của câu trả lời của mình, tôi sẽ nâng cấp của bạn và xóa của tôi.)
Richard Hansen

2
Tôi sẽ không bao giờ nghĩ về phương pháp này và tôi đã viết khoảng một nửa bộ tiền xử lý hiện tại của GCC! Điều đó nói rằng, tôi vẫn nói rằng "không có cách tiêu chuẩn nào để có được hiệu ứng này" bởi vì cả kỹ thuật của bạn và Richard đều áp đặt giới hạn trên về số lượng đối số cho macro.
zwol

2

Đây là phiên bản đơn giản hóa mà tôi sử dụng. Nó dựa trên các kỹ thuật tuyệt vời của các câu trả lời khác ở đây, rất nhiều đạo cụ cho chúng:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

Đó là nó.

Cũng như các giải pháp khác, điều này bị giới hạn ở số lượng đối số macro. Để hỗ trợ nhiều hơn, hãy thêm nhiều tham số vào _SELECTvà nhiều Nđối số hơn . Các tên đối số đếm ngược (thay vì lên) để phục vụ như một lời nhắc nhở rằng SUFFIXđối số dựa trên số lượng được cung cấp theo thứ tự ngược lại.

Giải pháp này xử lý 0 đối số như thể nó là 1 đối số. Vì vậy, BAR()trên danh nghĩa "hoạt động", bởi vì nó mở rộng đến _SELECT(_BAR,,N,N,N,N,1)(), mở rộng ra _BAR_1()(), mở rộng ra printf("\n").

Nếu bạn muốn, bạn có thể sáng tạo với việc sử dụng _SELECTvà cung cấp các macro khác nhau cho số lượng đối số khác nhau. Ví dụ: ở đây chúng tôi có macro LOG có đối số 'cấp' trước định dạng. Nếu định dạng bị thiếu, nó ghi nhật ký "(không có tin nhắn)", nếu chỉ có 1 đối số, nó sẽ ghi lại thông qua "% s", nếu không, nó sẽ coi đối số định dạng là chuỗi định dạng printf cho các đối số còn lại.

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

Điều này vẫn kích hoạt một cảnh báo khi được biên dịch với -pedantic.
PSkocik

1

Trong tình huống của bạn (có ít nhất 1 đối số hiện tại, không bao giờ 0), bạn có thể xác định BARBAR(...), sử dụng Jens Gustyt HAS_COMMA(...) để phát hiện dấu phẩy và sau đó gửi đến BAR0(Fmt)hoặc BAR1(Fmt,...)theo đó.

Điều này:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

biên dịch -pedanticmà không có một cảnh báo.


0

C (gcc) , 762 byte

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

Hãy thử trực tuyến!

Giả định:

  • Không có đối số chứa dấu phẩy hoặc dấu ngoặc
  • Không có arg chứa A~ G(có thể đổi tên thành hard_collide)

Giới no arg contain commahạn có thể được bỏ qua bằng cách kiểm tra đa sau một số lần vượt qua, nhưng no bracketvẫn còn đó
l4m2

-2

Giải pháp tiêu chuẩn là sử dụng FOOthay vì BAR. Có một vài trường hợp lập luận kỳ lạ sắp xếp lại có thể không phù hợp với bạn (mặc dù tôi cá là ai đó có thể đưa ra các cách hack thông minh để tháo gỡ và lắp ráp lại một cách có __VA_ARGS__điều kiện dựa trên số lượng đối số trong đó!) Nhưng nói chung sử dụng FOO"thường" chỉ hoạt động.


1
Câu hỏi đặt ra là "có cách nào tuân thủ tiêu chuẩn để có hành vi này không?"
Marsh Ray

2
Và câu hỏi đã bao gồm một lý do cho việc không sử dụng FOO cho lứa tuổi bây giờ.
Pavel imerda
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.