जीसीसी के ## __ VA_ARGS__ चाल के लिए मानक विकल्प?


151

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!",);

ध्यान रखें अल्पविराम - व्यावहारिक नहीं है।

कुछ संकलक (जैसे: विजुअल स्टूडियो 2010) चुपचाप आपके लिए उस अनुगामी अल्पविराम से छुटकारा दिलाएगा। अन्य संकलक (जैसे: GCC) समर्थन ##सामने रख रहे हैं __VA_ARGS__, जैसे:

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

लेकिन क्या इस व्यवहार को प्राप्त करने के लिए एक मानक-अनुपालन तरीका है? शायद कई मैक्रोज़ का उपयोग करना?

अभी, ##संस्करण काफी अच्छी तरह से समर्थित (कम से कम मेरे प्लेटफार्मों पर) लगता है, लेकिन मैं वास्तव में मानकों के अनुरूप समाधान का उपयोग करूंगा।

पूर्व-खाली: मुझे पता है कि मैं सिर्फ एक छोटा कार्य लिख सकता हूं। मैं मैक्रो का उपयोग करके ऐसा करने की कोशिश कर रहा हूं।

संपादित करें : यहां एक उदाहरण (हालांकि सरल) है कि मैं बार () का उपयोग क्यों करना चाहता हूं:

#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यह हमेशा डबल-उद्धृत सी-स्ट्रिंग है। यह न्यूलाइन को एक अलग प्रिंटफ () के रूप में मुद्रित नहीं करता है, जो लॉग-लाइन बफ़र किया गया है और कई स्रोतों से अतुल्यकालिक रूप से आ रहा है, तो यह फायदेमंद है।


3
पहली जगह के BARबजाय क्यों उपयोग करें FOO?
GManNickG

@ मन: मैंने एक उदाहरण जोड़ा
jwd

5
@ मन: आखिरी वाक्य पढ़ें (:
jwd


2
@zwol WG14 को प्रस्तुत नवीनतम संस्करण इस तरह दिखता है , जो __VA_OPT__कीवर्ड के आधार पर एक नए वाक्यविन्यास का उपयोग करता है । यह पहले से ही सी ++ द्वारा "अपनाया गया" है, इसलिए मुझे उम्मीद है कि सी सूट का पालन करेगा। (पता नहीं है कि इसका मतलब है कि यह C ++ 17 में तेजी से ट्रैक किया गया था या अगर यह C ++ 20 के लिए सेट किया गया है)
Leushenko

जवाबों:


66

जीसीसी के ,##__VA_ARGS__विस्तार के उपयोग से बचना संभव है यदि आप इस प्रश्न के उत्तर में रिचर्ड हैनसेन द्वारा वर्णित तर्कों की संख्या पर कुछ कठोर ऊपरी सीमा को स्वीकार करने के लिए तैयार हैं, जो आप अपने वैरिएड मैक्रो को पास कर सकते हैं । यदि आप ऐसी कोई सीमा नहीं रखना चाहते हैं, तथापि, मेरे सर्वोत्तम ज्ञान के लिए केवल C99- निर्दिष्ट प्रीप्रोसेसर सुविधाओं का उपयोग करना संभव नहीं है; आपको भाषा के लिए कुछ एक्सटेंशन का उपयोग करना चाहिए। clang और icc ने इस GCC एक्सटेंशन को अपनाया है, लेकिन MSVC ने नहीं।

2001 में वापस मैंने मानकीकरण के लिए जीसीसी एक्सटेंशन (और संबंधित एक्सटेंशन जो आपको __VA_ARGS__बाकी के पैरामीटर के अलावा अन्य नाम का उपयोग करने देता है ) को दस्तावेज़ N976 में लिखा था , लेकिन जो भी समिति से कोई प्रतिक्रिया नहीं मिली; मुझे यह भी नहीं पता कि कोई इसे पढ़े या नहीं। 2016 में इसे N2023 में फिर से प्रस्तावित किया गया था , और मैं किसी को भी प्रोत्साहित करता हूं जो जानता है कि यह प्रस्ताव हमें टिप्पणियों में कैसे पता चलेगा।


2
वेब पर समाधान खोजने के लिए मेरी विकलांगता को देखते हुए और उत्तर की कमी के कारण, मुझे लगता है कि आप सही हैं):
jwd

2
क्या n976 आप किसका जिक्र कर रहे हैं? मैं के बाकी की खोज सी कार्य समूह के दस्तावेजों एक प्रतिक्रिया के लिए, लेकिन एक कभी नहीं मिली। यह बाद की बैठक के एजेंडे में भी नहीं था । इस विषय पर केवल दूसरी हिट नॉर्वे की टिप्पणी # 4 थी n868 में C99 से पहले वापस पुष्टि की गई (फिर बिना किसी अनुवर्ती चर्चा के)।
रिचर्ड हैनसेन

4
हाँ, विशेष रूप से उस की दूसरी छमाही। इस पर चर्चा हो सकती है, comp.std.cलेकिन अभी मुझे Google समूह में कोई भी नहीं मिला था; यह निश्चित रूप से वास्तविक समिति से कभी भी ध्यान नहीं मिला (या अगर यह किया, किसी ने मुझे इसके बारे में कभी नहीं बताया)।
zwol

1
मुझे डर है कि मेरे पास कोई सबूत नहीं है, और न ही मैं अब किसी को सोचने की कोशिश करने के लिए सही व्यक्ति हूं। मैंने जीसीसी के प्रीप्रोसेसर का आधा हिस्सा लिखा था, लेकिन वह दस साल से अधिक समय पहले था, और मैंने कभी भी तर्क-गिनती की चाल के बारे में नहीं सोचा था, फिर भी।
zwol

6
यह एक्सटेंशन क्लैंग और इंटेल आईकस कंपाइलर के साथ-साथ gcc के साथ काम करता है।
ACIClic

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
ध्यान दें कि यदि आप 10 या अधिक तर्कों के साथ BAR को कॉल करते हैं, तो यह विफल हो जाएगा, और यद्यपि अधिक तर्कों का विस्तार करना अपेक्षाकृत आसान है, लेकिन यह हमेशा उन तर्कों की संख्या पर एक ऊपरी बाध्य होगा जो इसके साथ निपट सकते हैं
क्रिस डोड

2
@ क्रिसडोड: सही। दुर्भाग्य से, कंपाइलर-विशिष्ट एक्सटेंशनों पर भरोसा किए बिना तर्कों की संख्या में एक सीमा तक बचने का एक तरीका नहीं दिखाई देता है। इसके अलावा, अगर मैं बहुत सारे तर्क देता हूं, तो मज़बूती से परीक्षण करने के एक तरीके से अनजान हूं (ताकि एक उपयोगी संकलक त्रुटि संदेश मुद्रित किया जा सके, बल्कि एक अजीब विफलता)।
रिचर्ड हैनसेन

17

एक सामान्य समाधान नहीं है, लेकिन प्रिंटफ के मामले में आप एक नई रूपरेखा जोड़ सकते हैं:

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

मेरा मानना ​​है कि यह प्रारूप स्ट्रिंग में संदर्भित किसी भी अतिरिक्त आर्गन्स को अनदेखा नहीं करता है। तो आप भी शायद दूर हो सकते हैं:

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

मुझे विश्वास नहीं हो रहा है कि C99 को ऐसा करने के लिए एक मानक तरीके के बिना मंजूरी दी गई थी। AFAICT समस्या C ++ 11 में भी मौजूद है।


इस अतिरिक्त 0 के साथ समस्या यह है कि यह वास्तव में कोड में समाप्त हो जाएगा यदि इसे वार्ग फ़ंक्शन कहते हैं। रिचर्ड हैनसेन द्वारा प्रदान समाधान के लिए जाँच करें
पावेल पी।

@ पावेल दूसरे उदाहरण के बारे में सही है, लेकिन पहला काम बढ़िया है। +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 तर्क होता है, हम इसे दो "अधिभार" मैक्रोज़ के रूप में लागू कर सकते हैं:

#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()एक ही तर्क गिनती चाल का उपयोग करता है जिसे मैंने अपने जवाब में दर्ज किया था, और एक ही सीमा है (यह निश्चित संख्या से अधिक तर्क पारित होने पर टूट जाएगा)।
रिचर्ड हैनसेन

1
हां, मैंने देखा कि आपका दृष्टिकोण बूस्ट द्वारा उपयोग किया गया एक ही था, लेकिन बढ़ावा समाधान बहुत अच्छी तरह से बनाए रखा गया है, और अधिक परिष्कृत मैक्रो विकसित करते समय उपयोग के लिए अन्य बहुत उपयोगी विशेषताएं हैं। पुनरावर्तन का सामान विशेष रूप से ठंडा होता है (और अंतिम दृष्टिकोण में BOOST_PP_ARRAY_ENUM का उपयोग करने वाले दृश्यों के पीछे उपयोग किया जाता है)।
22 नवंबर को DRXX

1
बूस्ट का उत्तर जो वास्तव में सी टैग पर लागू होता है ! हुर्रे!
जस्टिन

6

डीबग प्रिंटिंग के लिए उपयोग होने वाला एक बहुत ही सरल मैक्रो:

#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तर्कों की संख्या को गिनने के लिए एक मैक्रो लिखने का एक तरीका है जिसे एक वैरिएड मैक्रो दिया गया है। आप NUM_ARGSनिर्माण की भिन्नता का उपयोग कर सकते हैं NUM_ARGS_CEILING2, जो आपको बता सकता है कि क्या एक वैरिएड मैक्रो को 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 का उपयोग नहीं किया गया है। मैंने इसे मूल चाल को स्पष्ट करने के लिए इसमें शामिल किया है। Jens Gustedt के P99 ब्लॉग को इसके अच्छे उपचार के लिए देखें ।

दो नोट:

  • NUM_ARGS उन तर्कों की संख्या में सीमित है जिन्हें वह संभालता है। मेरा केवल 20 तक ही संभाल सकता है, हालांकि संख्या पूरी तरह से मनमाना है।

  • NUM_ARGS, जैसा कि दिखाया गया है, इसमें 0 तर्क दिए जाने पर 1 रिटर्न होता है। इसका सार यह है कि NUM_ARGS तकनीकी रूप से [अल्पविराम + 1] की गिनती कर रहा है, और args नहीं। इस विशेष मामले में, यह वास्तव में हमारे लाभ के लिए काम करता है। _UNIMPLEMENTED1 एक खाली टोकन को ठीक से संभालेगा और यह हमें _UNIMPLEMENTED0 लिखने से बचाता है। Gustedt के पास इसके लिए एक वर्कअराउंड है, हालांकि मैंने इसका उपयोग नहीं किया है और मुझे यकीन नहीं है कि अगर हम यहां काम कर रहे हैं तो यह काम करेगा।


+1 तर्क गिनती की चाल लाने के लिए, -1 का पालन करने के लिए वास्तव में कठिन होने के लिए
रिचर्ड हेनन

आपके द्वारा जोड़ी गई टिप्पणियां एक सुधार थीं, लेकिन अभी भी कई समस्याएं हैं: 1. आप चर्चा करते हैं और परिभाषित NUM_ARGSकरते हैं लेकिन इसका उपयोग नहीं करते हैं। 2. क्या उद्देश्य है UNIMPLEMENTED? 3. आप प्रश्न में उदाहरण समस्या का समाधान कभी नहीं करते हैं। 4. एक समय में विस्तार के माध्यम से चलना यह बताता है कि यह कैसे काम करता है और प्रत्येक सहायक मैक्रो की भूमिका की व्याख्या करता है। 5. 0 तर्कों पर चर्चा करना विचलित करने वाला है; ओपी मानकों के अनुपालन के बारे में पूछ रहा था, और 0 तर्क निषिद्ध हैं (C99 6.10.3p4)। 6. चरण 1.5? 2 कदम क्यों नहीं? 7. "कदम" का तात्पर्य है कि क्रम से होने वाली क्रियाएं; यह सिर्फ कोड है।
रिचर्ड हैनसन

8. आप पूरे ब्लॉग से लिंक करते हैं, प्रासंगिक पोस्ट से नहीं। मैं उस पद को नहीं पा सका जिसका आप उल्लेख कर रहे थे। 9. अंतिम अनुच्छेद अजीब है: यह विधि है अस्पष्ट; इसलिए पहले किसी और ने सही समाधान नहीं पोस्ट किया था। इसके अलावा, अगर यह काम करता है और मानक का पालन करता है, तो जैक का जवाब गलत होना चाहिए। 10. आपको परिभाषित करना चाहिए CONCAT()- यह मत समझना कि पाठकों को पता है कि यह कैसे काम करता है।
रिचर्ड हैनसेन

(कृपया इस प्रतिक्रिया को एक हमले के रूप में व्याख्या न करें - मैं वास्तव में आपके उत्तर को उभारना चाहता था लेकिन ऐसा करने में सहज महसूस नहीं करता था, जब तक कि इसे समझना आसान न हो। यदि आप अपने उत्तर की स्पष्टता में सुधार कर सकते हैं, तो मैं करूँगा। तुम्हारा उत्थान करो और मेरा उद्धार करो।)
रिचर्ड हैनसेन

2
मैंने इस दृष्टिकोण के बारे में कभी नहीं सोचा होगा, और मैंने जीसीसी के वर्तमान प्रीप्रोसेसर का लगभग आधा लिखा था! उस ने कहा, मैं अभी भी कहता हूं कि "इस प्रभाव को प्राप्त करने का कोई मानक तरीका नहीं है" क्योंकि आपकी और रिचर्ड की तकनीक दोनों मैक्रो के लिए तर्कों की संख्या पर ऊपरी सीमा लगाती हैं।
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और विभिन्न प्रकार के तर्कों के लिए विभिन्न मैक्रो प्रदान कर सकते हैं । उदाहरण के लिए, यहां हमारे पास एक लॉग मैक्रो है जो प्रारूप से पहले 'स्तर' तर्क लेता है। यदि प्रारूप गायब है, तो यह "(कोई संदेश नहीं)" लॉग करता है, यदि सिर्फ 1 तर्क है, तो वह इसे "% s" के माध्यम से लॉग करेगा, अन्यथा यह शेष तर्कों के लिए प्रारूप तर्क को एक प्रिंटफ प्रारूप स्ट्रिंग के रूप में मानेगा।

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

यह अभी भी एक चेतावनी को ट्रिगर करता है जब -पेटिकल के साथ संकलित किया जाता है।
पीएसकोकिक

1

आपकी स्थिति में (कम से कम 1 तर्क मौजूद है, कभी 0 नहीं), आप के BARरूप में परिभाषित कर सकते हैं BAR(...), कॉन्स का 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

सी (जीसीसी) , 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 A~ नहीं होते हैं G(hard_collide वाले का नाम बदल सकते हैं)

no arg contain commaसीमा कुछ और गुजरता के बाद बहु की जाँच करके नजरअंदाज किया जा सकता है, लेकिन no bracketअभी भी वहाँ
l4m2

-2

मानक समाधान के FOOबजाय उपयोग करना है BAR। तर्क के कुछ अजीब मामले हैं जो इसे शायद आपके लिए नहीं कर सकते हैं (हालांकि मुझे यकीन है कि कोई व्यक्ति चतुर हैक करने के लिए आ सकता है और __VA_ARGS__इसमें तर्क की संख्या के आधार पर सशर्त रूप से जुदा करने और फिर से इकट्ठा करने के लिए हो सकता है!) लेकिन सामान्य FOOतौर पर आमतौर पर इसका उपयोग किया जाता है। बस काम करता है।


1
सवाल था "क्या इस व्यवहार को प्राप्त करने के लिए एक मानक-अनुपालन तरीका है?"
मार्श रे

2
और सवाल में अब उम्र के लिए FOO का उपयोग नहीं करने के लिए एक तर्क शामिल है।
पावेल Paमरदा '
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.