ทางเลือกมาตรฐานของเคล็ดลับ ## __ VA_ARGS__ ของ GCC?


151

มีปัญหาที่รู้จักกันดี กับ args ที่ว่างเปล่าสำหรับแมโคร variadic ใน C99

ตัวอย่าง:

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

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

การใช้งานBAR()ด้านบนนั้นไม่ถูกต้องตามมาตรฐาน C99 เนื่องจากจะขยายเป็น:

printf("this breaks!",);

สังเกตเครื่องหมายจุลภาคต่อท้าย - ใช้งานไม่ได้

คอมไพเลอร์บางตัว (เช่น: Visual Studio 2010) จะกำจัดเครื่องหมายจุลภาคที่ต่อท้ายให้คุณอย่างเงียบ ๆ คอมไพเลอร์อื่น ๆ (เช่น: GCC) รองรับการวางไว้##ด้านหน้า__VA_ARGS__เช่น:

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

แต่มีวิธีที่เป็นไปตามมาตรฐานในการรับพฤติกรรมนี้หรือไม่? บางทีใช้มาโครหลายตัว?

ตอนนี้##รุ่นดูเหมือนจะได้รับการสนับสนุนอย่างดี (อย่างน้อยบนแพลตฟอร์มของฉัน) แต่ฉันควรใช้โซลูชันที่ได้มาตรฐาน

การจองล่วงหน้า: ฉันรู้ว่าฉันสามารถเขียนฟังก์ชันเล็ก ๆ ได้ ฉันพยายามทำสิ่งนี้โดยใช้มาโคร

แก้ไข : นี่คือตัวอย่าง (แต่ง่าย ๆ ) ว่าทำไมฉันถึงต้องการใช้ 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);

สิ่งนี้จะเพิ่มบรรทัดใหม่ให้กับคำสั่งการบันทึก BAR () ของฉันโดยอัตโนมัติโดยถือว่าfmtเป็นสตริง C สองครั้ง มันไม่ได้พิมพ์ขึ้นบรรทัดใหม่เป็น printf () แยกต่างหากซึ่งเป็นข้อได้เปรียบหากการบันทึกถูกแบ่งบรรทัดและมาจากหลายแหล่งพร้อมกัน


3
ทำไมต้องใช้BARแทนที่จะFOOเป็นที่แรก?
GManNickG

@GMan: ฉันเพิ่มตัวอย่างในตอนท้าย
jwd

5
@GMan: อ่านประโยคสุดท้าย (:
jwd


2
@zwol เวอร์ชันล่าสุดที่ส่งไปยัง WG14 มีลักษณะเช่นนี้ซึ่งใช้ไวยากรณ์ใหม่ตาม__VA_OPT__คำหลัก สิ่งนี้ได้ถูก "นำไปใช้"โดย C ++ ดังนั้นฉันคาดว่า C จะเป็นไปตามความเหมาะสม (ไม่ทราบว่านั่นหมายความว่ามีการติดตามอย่างรวดเร็วใน C ++ 17 หรือถ้าตั้งไว้สำหรับ C ++ 20)
Leushenko

คำตอบ:


66

มันเป็นไปได้ที่จะหลีกเลี่ยงการใช้ GCC ที่,##__VA_ARGS__ขยายถ้าคุณมีความเต็มใจที่จะยอมรับบางขีด จำกัด บน hardcoded กับจำนวนของการขัดแย้งที่คุณสามารถส่งผ่านไปยังแมโคร variadic ของคุณตามที่อธิบายไว้ในคำตอบของริชาร์ดแฮนเซนกับคำถามนี้ หากคุณไม่ต้องการมีข้อ จำกัด ดังกล่าวอย่างไรก็ตามเท่าที่ฉันทราบดีที่สุดแล้วคุณจะไม่สามารถใช้คุณสมบัติตัวประมวลผลล่วงหน้าที่ระบุไว้ใน C99 เท่านั้น คุณต้องใช้ส่วนขยายบางอย่างกับภาษา clang และ icc ได้ใช้ส่วนขยาย GCC นี้ แต่ MSVC ไม่มี

ย้อนกลับไปในปี 2544 ฉันได้เขียนส่วนขยาย GCC สำหรับมาตรฐาน (และส่วนขยายที่เกี่ยวข้องที่ให้คุณใช้ชื่ออื่นที่ไม่ใช่__VA_ARGS__พารามิเตอร์ส่วนที่เหลือ) ในเอกสาร N976แต่ไม่ได้รับการตอบสนองใด ๆ จากคณะกรรมการ ฉันไม่รู้ด้วยซ้ำว่ามีใครอ่านบ้าง ในปี 2559 มีการเสนออีกครั้งในN2023และฉันขอแนะนำให้ทุกคนที่รู้ว่าข้อเสนอนั้นจะแจ้งให้เราทราบในความคิดเห็นอย่างไร


2
ตัดสินโดยความพิการของฉันที่จะหาทางออกบนเว็บและการขาดคำตอบที่นี่ฉันเดาว่าคุณพูดถูก):
jwd

2
คือn976สิ่งที่คุณกำลังหมายถึง? ฉันค้นหาส่วนที่เหลือของกลุ่ม C การทำงานของเอกสารสำหรับการตอบสนอง แต่ไม่เคยพบหนึ่ง มันไม่ได้อยู่ในวาระการประชุมครั้งต่อไป สิ่งที่ฮิตอื่น ๆ ในหัวข้อนี้คือความเห็นของนอร์เวย์ที่ 4 ในn868กลับมาก่อนที่ C99 จะได้รับการให้สัตยาบัน (อีกครั้งโดยไม่มีการสนทนาติดตาม)
Richard Hansen

4
ใช่โดยเฉพาะช่วงครึ่งหลังของปีนั้น อาจมีการพูดคุยกันcomp.std.cแต่ตอนนี้ฉันไม่พบสิ่งใดใน Google Groups แน่นอนว่ามันไม่เคยได้รับความสนใจใด ๆ จากคณะกรรมการที่แท้จริง (หรือถ้าเป็นเช่นนั้น
zwol

1
ฉันเกรงว่าฉันจะไม่มีหลักฐานและฉันก็ไม่ใช่คนที่เหมาะสมที่จะลองคิด ฉันเขียนตัวประมวลผลล่วงหน้าของ GCC ครึ่งหนึ่ง แต่นั่นเป็นเวลากว่าสิบปีที่ผ่านมาและฉันไม่เคยนึกถึงกลวิธีการนับอาร์กิวเมนต์ด้านล่างเลยแม้แต่น้อย
zwol

6
ส่วนขยายนี้ทำงานร่วมกับคอมไพเลอร์ clang & intel icc เช่นเดียวกับ gcc
วัฏจักร

112

มีเคล็ดลับการนับอาร์กิวเมนต์ที่คุณสามารถใช้ได้

ต่อไปนี้เป็นวิธีมาตรฐานที่ใช้ในการใช้BAR()ตัวอย่างที่สองในคำถามของ 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;
}

เคล็ดลับเดียวกันนี้ใช้เพื่อ:

คำอธิบาย

กลยุทธ์คือการแยกออก__VA_ARGS__เป็นอาร์กิวเมนต์แรกและส่วนที่เหลือ (ถ้ามี) สิ่งนี้ทำให้เป็นไปได้ที่จะแทรกสิ่งต่าง ๆ หลังจากอาร์กิวเมนต์แรก แต่ก่อนที่สอง (ถ้ามี)

FIRST()

มาโครนี้จะขยายไปยังอาร์กิวเมนต์แรกโดยทิ้งส่วนที่เหลือ

การดำเนินการเป็นเรื่องง่าย throwawayโต้แย้งเพื่อให้แน่ใจว่าFIRST_HELPER()ได้รับสองข้อโต้แย้งซึ่งเป็นสิ่งจำเป็นเพราะ...ความต้องการที่หนึ่งอย่างน้อย ด้วยหนึ่งอาร์กิวเมนต์มันจะขยายดังนี้:

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

ด้วยอย่างน้อยสองรายการจึงขยายเป็นดังนี้:

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

REST()

แมโครนี้ขยายทุกอย่างยกเว้นอาร์กิวเมนต์แรก (รวมถึงเครื่องหมายจุลภาคหลังอาร์กิวเมนต์แรกหากมีมากกว่าหนึ่งอาร์กิวเมนต์)

การใช้งานแมโครนี้มีความซับซ้อนมากขึ้น กลยุทธ์ทั่วไปคือการนับจำนวนอาร์กิวเมนต์ (หนึ่งหรือมากกว่าหนึ่ง) จากนั้นขยายเป็นREST_HELPER_ONE()(ถ้ามีเพียงหนึ่งอาร์กิวเมนต์ที่กำหนด) หรือREST_HELPER_TWOORMORE()(ถ้ามีอาร์กิวเมนต์มากกว่าสองรายการ) REST_HELPER_ONE()เพียงแค่ขยายออกเป็นไม่มีอะไร - ไม่มีข้อโต้แย้งหลังจากแรกดังนั้นอาร์กิวเมนต์ที่เหลือคือชุดที่ว่างเปล่า REST_HELPER_TWOORMORE()ตรงไปตรงมา - มันขยายไปถึงเครื่องหมายจุลภาคตามด้วยทุกอย่างยกเว้นอาร์กิวเมนต์แรก

อาร์กิวเมนต์จะถูกนับโดยใช้NUM()แมโคร แมโครนี้จะขยายเป็นONEถ้ามีเพียงอาร์กิวเมนต์เดียวเท่านั้นที่กำหนดTWOORMOREหากมีการกำหนดอาร์กิวเมนต์ระหว่างสองถึงเก้าข้อและแบ่งหากมีการกำหนดอาร์กิวเมนต์ตั้งแต่ 10 ข้อขึ้นไป (เพราะจะขยายเป็นอาร์กิวเมนต์ที่ 10)

NUM()แมโครใช้SELECT_10TH()แมโครเพื่อกำหนดจำนวนของการขัดแย้ง ตามที่ชื่อมีความหมายSELECT_10TH()เพียงแค่ขยายไปยังอาร์กิวเมนต์ที่ 10 เนื่องจากจุดไข่ปลาSELECT_10TH()ต้องผ่านการโต้แย้งอย่างน้อย 11 ครั้ง (มาตรฐานบอกว่าต้องมีอย่างน้อยหนึ่งอาร์กิวเมนต์สำหรับจุดไข่ปลา) นี่คือเหตุผลที่NUM()ส่งผ่านthrowawayเป็นอาร์กิวเมนต์สุดท้าย (โดยไม่มีการส่งผ่านอาร์กิวเมนต์หนึ่งNUM()จะส่งผลให้มีเพียง 10 ข้อโต้แย้งที่ถูกส่งผ่านไปSELECT_10TH()ซึ่งจะละเมิดมาตรฐาน)

การเลือกอย่างใดอย่างหนึ่งREST_HELPER_ONE()หรือREST_HELPER_TWOORMORE()จะกระทำโดยการเชื่อมโยงREST_HELPER_กับการขยายตัวของในNUM(__VA_ARGS__) REST_HELPER2()โปรดทราบว่าวัตถุประสงค์ของการREST_HELPER()เพื่อให้มั่นใจว่าจะขยายตัวได้อย่างเต็มที่ก่อนที่จะถูกตัดแบ่งกับNUM(__VA_ARGS__)REST_HELPER_

การขยายด้วยอาร์กิวเมนต์หนึ่งตัวจะเป็นดังนี้:

  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. (ว่าง)

การขยายด้วยอาร์กิวเมนต์สองตัวขึ้นไปจะเป็นดังนี้:

  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
โปรดทราบว่าสิ่งนี้จะล้มเหลวหากคุณเรียก BAR ด้วยอาร์กิวเมนต์ 10 ตัวขึ้นไปและถึงแม้ว่ามันจะค่อนข้างง่ายที่จะขยายไปสู่การโต้แย้งมากกว่า แต่มันก็จะมีขอบเขตบนของจำนวนการขัดแย้งที่สามารถจัดการได้
Chris Dodd

2
@ChrisDodd: ถูกต้อง น่าเสียดายที่ดูเหมือนจะไม่มีทางหลีกเลี่ยงข้อ จำกัด ในจำนวนอาร์กิวเมนต์โดยไม่ต้องอาศัยส่วนขยายเฉพาะคอมไพเลอร์ นอกจากนี้ฉันไม่ทราบวิธีทดสอบที่เชื่อถือได้หากมีข้อโต้แย้งมากเกินไป (เพื่อให้ข้อความแสดงข้อผิดพลาดคอมไพเลอร์ที่มีประโยชน์สามารถพิมพ์ได้
Richard Hansen

17

ไม่ใช่โซลูชันทั่วไป แต่ในกรณีของ printf คุณสามารถเพิ่มบรรทัดใหม่เช่น:

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

ฉันเชื่อว่ามันละเว้น args พิเศษใด ๆ ที่ไม่ได้อ้างอิงในสตริงรูปแบบ ดังนั้นคุณอาจจะได้ไปกับ:

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

ฉันไม่อยากเชื่อเลยว่า C99 ได้รับการอนุมัติโดยไม่มีวิธีมาตรฐานในการทำเช่นนี้ AFAICT ปัญหามีอยู่ใน C ++ 11 เช่นกัน


ปัญหาของ 0 พิเศษนี้คือมันจะลงเอยในรหัสจริง ๆ ถ้ามันเรียกฟังก์ชัน vararg ตรวจสอบวิธีการแก้ปัญหาของ Richard Hansen
Pavel P

@Pavel ถูกต้องเกี่ยวกับตัวอย่างที่สอง แต่ครั้งแรกใช้งานได้ดี +1
kirbyfan64sos

11

ไม่มีทางที่จะจัดการกับกรณีนี้โดยเฉพาะโดยใช้สิ่งที่ต้องการคือBoost.Preprocessor คุณสามารถใช้BOOST_PP_VARIADIC_SIZEเพื่อตรวจสอบขนาดของรายการอาร์กิวเมนต์แล้วขยายตามเงื่อนไขไปยังแมโครอื่น ข้อบกพร่องอย่างหนึ่งของเรื่องนี้คือมันไม่สามารถแยกแยะความแตกต่างระหว่างอาร์กิวเมนต์ 0 และ 1 ได้และเหตุผลของสิ่งนี้ชัดเจนเมื่อคุณพิจารณาสิ่งต่อไปนี้:

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

รายการอาร์กิวเมนต์ของแมโครที่ว่างเปล่าจริง ๆ แล้วประกอบด้วยหนึ่งอาร์กิวเมนต์ที่เกิดขึ้นเป็นค่าว่าง

ในกรณีนี้เราโชคดีเนื่องจากแมโครที่คุณต้องการมีอาร์กิวเมนต์อย่างน้อย 1 ตัวเสมอเราสามารถใช้มันเป็นมาโคร "overload" สองตัว:

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

จากนั้นแมโครอื่นเพื่อสลับไปมาระหว่างกันเช่น:

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

หรือ

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

แล้วแต่ว่าคุณจะอ่านอะไรได้มากกว่านี้ (ฉันชอบอันแรกมากกว่าเพราะมันให้รูปแบบทั่วไปสำหรับการโหลดมาโครมากเกินไปในจำนวนอาร์กิวเมนต์)

นอกจากนี้ยังเป็นไปได้ที่จะทำเช่นนี้ด้วยแมโครเดียวโดยการเข้าถึงและการกลายพันธุ์รายการอาร์กิวเมนต์ของตัวแปร แต่มันเป็นวิธีที่อ่านได้น้อยลงและมีความเฉพาะเจาะจงมากกับปัญหานี้:

#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__)))) \
    /**/

นอกจากนี้ทำไมไม่มี BOOST_PP_ARRAY_ENUM_TRAILING มันจะทำให้ทางออกนี้น่ากลัวน้อยลง

แก้ไข: เอาล่ะนี่คือ BOOST_PP_ARRAY_ENUM_TRAILING และรุ่นที่ใช้ (นี่คือโซลูชันที่ฉันโปรดปราน):

#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
ยินดีที่ได้เรียนรู้เกี่ยวกับ Boost.Preprocessor, +1 โปรดทราบว่าBOOST_PP_VARIADIC_SIZE()ใช้เคล็ดลับการนับอาร์กิวเมนต์เดียวกันฉันบันทึกไว้ในคำตอบของฉันและมีข้อ จำกัด เดียวกัน (มันจะแตกถ้าคุณผ่านการโต้แย้งมากกว่าจำนวนหนึ่ง)
Richard Hansen

1
ใช่ฉันเห็นว่าวิธีการของคุณเป็นแบบเดียวกับที่ใช้โดย Boost แต่โซลูชันเพิ่มได้รับการดูแลอย่างดีและมีคุณสมบัติที่มีประโยชน์อื่น ๆ อีกมากมายสำหรับการใช้งานเมื่อพัฒนาแมโครที่ซับซ้อนยิ่งขึ้น เนื้อหาการเรียกซ้ำนั้นยอดเยี่ยมโดยเฉพาะ (และใช้เบื้องหลังในแนวทางสุดท้ายที่ใช้ BOOST_PP_ARRAY_ENUM)
DRayX

1
คำตอบ Boost ที่ใช้กับแท็กcจริง ๆ! ไชโย!
Justin

6

แมโครที่ง่ายมากที่ฉันใช้สำหรับการพิมพ์ debug:

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

ไม่ว่าจะมีการส่งผ่านข้อโต้แย้งไปยัง DBG จำนวนเท่าใดก็ไม่มีคำเตือน c99

เคล็ดลับคือ__DBG_INTการเพิ่มหุ่นจำลองดังนั้น...จะมีอาร์กิวเมนต์อย่างน้อยหนึ่งครั้งและมีความพึงพอใจ c99


5

ฉันพบปัญหาที่คล้ายกันเมื่อเร็ว ๆ นี้และฉันเชื่อว่ามีวิธีแก้ปัญหา

แนวคิดหลักคือมีวิธีการเขียนแมโครNUM_ARGSเพื่อนับจำนวนอาร์กิวเมนต์ที่ให้มาโครแบบ Variadic คุณสามารถใช้ชุดรูปแบบของNUM_ARGSการสร้างNUM_ARGS_CEILING2ซึ่งสามารถบอกคุณได้ว่ามีมาโครแบบ Variadic 1 อาร์กิวเมนต์หรือ 2 หรือมากกว่านั้น จากนั้นคุณสามารถเขียนBarแมโครของคุณเพื่อที่จะใช้NUM_ARGS_CEILING2และCONCATส่งอาร์กิวเมนต์ไปยังหนึ่งในสองแมโครตัวช่วย: หนึ่งตัวซึ่งคาดว่าจะมีอาร์กิวเมนต์ 1 ตัวและอีกอันที่คาดว่าจะมีจำนวนตัวแปรที่มากกว่า 1

นี่คือตัวอย่างที่ฉันใช้เคล็ดลับนี้เพื่อเขียนแมโครUNIMPLEMENTEDซึ่งคล้ายกับBAR:

ขั้นตอนที่ 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

ขั้นตอนที่ 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)

ขั้นตอนที่ 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__)

ขั้นตอนที่ 3:

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

ตำแหน่งที่ CONCAT ถูกนำไปใช้ในวิธีปกติ ตามคำแนะนำสั้น ๆ หากสิ่งที่กล่าวมาข้างต้นทำให้เกิดความสับสน: เป้าหมายของ CONCAT นั้นมีเป้าหมายที่จะขยายไปสู่การ "เรียก" แมโครอื่น

โปรดทราบว่าไม่ได้ใช้ NUM_ARGS ตัวเอง ฉันเพิ่งรวมมันเพื่อแสดงให้เห็นถึงเคล็ดลับพื้นฐานที่นี่ ดูบล็อก P99 ของ Jens Gustedtเพื่อรับการรักษาที่ดี

หมายเหตุสองประการ:

  • NUM_ARGS ถูก จำกัด ในจำนวนอาร์กิวเมนต์ที่จัดการ ฉันสามารถจัดการได้มากถึง 20 ตัวแม้ว่าจำนวนจะเป็นไปโดยพลการก็ตาม

  • NUM_ARGS ตามที่ปรากฏมีข้อผิดพลาดในการที่มันจะส่งกลับ 1 เมื่อได้รับ 0 ข้อโต้แย้ง ส่วนสำคัญของมันคือ NUM_ARGS มีการนับในทางเทคนิค [คอมม่า + 1] และไม่ใช่ args ในกรณีพิเศษนี้ใช้งานได้จริงกับประโยชน์ของเรา _UNIMPLEMENTED1 จะจัดการโทเค็นที่ว่างเปล่าได้ดีและช่วยให้เราไม่ต้องเขียน _UNIMPLEMENTED0 Gustedt มีวิธีแก้ปัญหาสำหรับสิ่งนั้นเช่นกันแม้ว่าฉันจะไม่ได้ใช้มันและฉันก็ไม่แน่ใจว่ามันจะใช้ได้ผลกับสิ่งที่เราทำที่นี่หรือไม่


+1 สำหรับการนำเสนอเคล็ดลับการนับอาร์กิวเมนต์ -1 สำหรับการติดตามยากจริงๆ
Richard Hansen

ความคิดเห็นที่คุณเพิ่มเป็นการปรับปรุง แต่ยังคงมีปัญหาอยู่หลายประการ: 1. คุณพูดคุยและกำหนดNUM_ARGSแต่ไม่ได้ใช้ 2. มีวัตถุประสงค์UNIMPLEMENTEDอะไร? 3. คุณไม่เคยแก้ปัญหาตัวอย่างในคำถาม 4. การเดินผ่านส่วนขยายทีละขั้นจะแสดงให้เห็นว่ามันทำงานอย่างไรและอธิบายบทบาทของแมโครตัวช่วยแต่ละตัว 5. การพูดคุย 0 ข้อโต้แย้งทำให้เสียสมาธิ OP ได้ถามเกี่ยวกับการปฏิบัติตามมาตรฐานและห้ามมีการโต้แย้ง 0 ครั้ง (C99 6.10.3p4) 6. ขั้นตอนที่ 1.5? ทำไมไม่ทำตามขั้นตอนที่ 2 7. "ขั้นตอน" หมายถึงการกระทำที่เกิดขึ้นตามลำดับ; นี่เป็นเพียงรหัส
Richard Hansen

8. คุณลิงก์ไปยังบล็อกทั้งหมดไม่ใช่โพสต์ที่เกี่ยวข้อง ฉันไม่พบโพสต์ที่คุณอ้างถึง 9. ย่อหน้าสุดท้ายอึดอัดใจ: วิธีนี้ไม่ชัดเจน; นั่นเป็นเหตุผลที่ไม่มีใครเคยโพสต์โซลูชันที่ถูกต้องมาก่อน นอกจากนี้หากการทำงานและปฏิบัติตามมาตรฐานคำตอบของ Zack จะต้องผิด 10. คุณควรนิยามCONCAT()- อย่าถือว่าผู้อ่านรู้ว่ามันทำงานอย่างไร
Richard Hansen

(โปรดอย่าตีความข้อเสนอแนะนี้เป็นการโจมตี - ฉันต้องการอัปเกรดคำตอบของคุณ แต่ไม่สะดวกที่จะทำเช่นนั้นเว้นแต่จะเข้าใจได้ง่ายขึ้นหากคุณสามารถปรับปรุงความชัดเจนของคำตอบฉันจะ โหวตให้คุณและลบของฉัน)
Richard Hansen

2
ฉันจะไม่คิดถึงวิธีการนี้และฉันได้เขียนประมาณครึ่งหนึ่งของตัวประมวลผลล่วงหน้าปัจจุบันของ GCC! ที่กล่าวว่าฉันยังคงพูดว่า "ไม่มีวิธีมาตรฐานในการรับผลกระทบนี้" เพราะทั้งเทคนิคของคุณและ Richard กำหนดขีด จำกัด สูงสุดของจำนวนการขัดแย้งกับมาโคร
zwol

2

นี่เป็นเวอร์ชั่นที่เรียบง่ายที่ฉันใช้ มันขึ้นอยู่กับเทคนิคที่ยอดเยี่ยมของคำตอบอื่น ๆ ที่นี่อุปกรณ์ประกอบฉากมากมายสำหรับพวกเขา:

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

แค่นั้นแหละ.

เช่นเดียวกับโซลูชันอื่น ๆ สิ่งนี้ จำกัด เฉพาะจำนวนอาร์กิวเมนต์ที่มาโคร หากต้องการสนับสนุนเพิ่มเติมให้เพิ่มพารามิเตอร์_SELECTมากขึ้นและเพิ่มNอาร์กิวเมนต์เพิ่มเติม ชื่ออาร์กิวเมนต์นับถอยหลัง (แทนขึ้น) เพื่อใช้เป็นตัวเตือนว่ามีการระบุSUFFIXอาร์กิวเมนต์ตามจำนวนไว้ในลำดับย้อนกลับ

วิธีการแก้ปัญหานี้ปฏิบัติ 0 ข้อโต้แย้งราวกับว่ามันเป็น 1 ข้อโต้แย้ง ดังนั้นBAR()ในนาม "ผลงาน" เพราะมันขยายไป_SELECT(_BAR,,N,N,N,N,1)()ซึ่งขยายไปซึ่งจะขยาย_BAR_1()()printf("\n")

หากคุณต้องการคุณสามารถสร้างสรรค์ด้วยการใช้_SELECTและให้มาโครที่แตกต่างกันสำหรับจำนวนอาร์กิวเมนต์ที่ต่างกัน ตัวอย่างเช่นที่นี่เรามีมาโคร LOG ที่ใช้อาร์กิวเมนต์ 'ระดับ' ก่อนรูปแบบ หากรูปแบบหายไปมันจะบันทึก "(ไม่มีข้อความ)" หากมีเพียง 1 อาร์กิวเมนต์มันจะบันทึกผ่าน "% s" มิฉะนั้นจะถือว่าอาร์กิวเมนต์รูปแบบเป็นสตริงรูปแบบ printf สำหรับอาร์กิวเมนต์ที่เหลือ

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

สิ่งนี้ยังคงทริกเกอร์คำเตือนเมื่อคอมไพล์ด้วย -pedantic
PSkocik

1

ในสถานการณ์ของคุณ (มีอาร์กิวเมนต์อย่างน้อย 1 ข้อไม่เคยเป็น 0) คุณสามารถกำหนดBARเป็นBAR(...)ใช้Jens Gustedt HAS_COMMA(...)เพื่อตรวจหาเครื่องหมายจุลภาคแล้วส่งไปBAR0(Fmt)หรือBAR1(Fmt,...)ตามนั้น

นี้:

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

คอมไพล์ด้วย-pedanticไม่มีคำเตือน


0

C (gcc) , 762 ไบต์

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

ลองออนไลน์!

ถือว่า:

  • ไม่มี arg มีเครื่องหมายจุลภาคหรือวงเล็บ
  • ไม่มี arg ประกอบด้วยA~ G(สามารถเปลี่ยนชื่อเป็น hard_collide ได้)

no arg contain commaข้อ จำกัด อาจจะข้ามโดยการตรวจสอบหลายหลังจากผ่านบางมากขึ้น แต่no bracketยังคงมี
l4m2

-2

สารละลายมาตรฐานคือการใช้แทนFOO BARมีบางกรณีของการโต้แย้งการเรียงลำดับใหม่มันอาจไม่สามารถทำเพื่อคุณ (แม้ว่าฉันเดิมพันคนที่สามารถเกิดขึ้นกับแฮ็คที่ฉลาดในการแยกและประกอบใหม่__VA_ARGS__ตามเงื่อนไขตามจำนวนของข้อโต้แย้งในมัน!) แต่โดยทั่วไปใช้FOO"ปกติ" เพิ่งได้ผล


1
คำถามคือ "มีวิธีที่เป็นไปตามมาตรฐานเพื่อให้ได้พฤติกรรมนี้หรือไม่"
Marsh Ray

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