Alternatif standar untuk trik ## __ VA_ARGS__ GCC?


151

Ada masalah terkenal dengan arg kosong untuk makro variadic di C99.

contoh:

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

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

Penggunaan di BAR()atas memang salah menurut standar C99, karena akan diperluas ke:

printf("this breaks!",);

Perhatikan tanda koma - tidak bisa digunakan.

Beberapa kompiler (mis: Visual Studio 2010) akan dengan tenang menyingkirkan koma jejak itu untuk Anda. Kompiler lain (misalnya: GCC) mendukung meletakkan ##di depan __VA_ARGS__, seperti:

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

Tetapi apakah ada cara yang sesuai standar untuk mendapatkan perilaku ini? Mungkin menggunakan beberapa makro?

Saat ini, ##versi ini tampaknya cukup didukung (setidaknya pada platform saya), tetapi saya lebih suka menggunakan solusi yang sesuai standar.

Pre-emptive: Saya tahu saya hanya bisa menulis fungsi kecil. Saya mencoba melakukan ini menggunakan makro.

Sunting : Ini adalah contoh (walaupun sederhana) mengapa saya ingin menggunakan 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);

Ini secara otomatis menambahkan baris baru ke pernyataan logging BAR () saya, dengan asumsi fmtselalu merupakan C-string yang dikutip ganda. Ini TIDAK mencetak baris baru sebagai printf terpisah (), yang menguntungkan jika logging-buffered dan berasal dari berbagai sumber secara tidak sinkron.


3
Mengapa menggunakan BARdaripada FOOdi tempat pertama?
GManNickG

@ GM: Saya menambahkan contoh di akhir
jwd

5
@ GM: Baca kalimat terakhir (:
jwd


2
@ zwol versi terbaru yang dikirimkan ke WG14 terlihat seperti ini , yang menggunakan sintaksis baru berdasarkan __VA_OPT__kata kunci. Ini sudah "diadopsi" oleh C ++, jadi saya berharap C akan mengikutinya. (tidak tahu apakah itu berarti itu dilacak cepat ke C ++ 17 atau jika itu ditetapkan untuk C ++ 20 sekalipun)
Leushenko

Jawaban:


66

Dimungkinkan untuk menghindari penggunaan ,##__VA_ARGS__ekstensi GCC jika Anda bersedia menerima beberapa batas atas yang dikodekan pada jumlah argumen yang dapat Anda sampaikan ke makro variadic Anda, seperti yang dijelaskan dalam jawaban Richard Hansen untuk pertanyaan ini . Namun, jika Anda tidak ingin memiliki batas seperti itu, sejauh yang saya ketahui, tidak mungkin hanya menggunakan fitur preprosesor yang ditentukan C99; Anda harus menggunakan beberapa ekstensi ke bahasa. dentang dan icc telah mengadopsi ekstensi GCC ini, tetapi MSVC belum.

Kembali pada tahun 2001 saya menulis ekstensi GCC untuk standardisasi (dan ekstensi terkait yang memungkinkan Anda menggunakan nama selain __VA_ARGS__untuk parameter-sisanya) dalam dokumen N976 , tetapi yang tidak menerima respons apa pun dari komite; Saya bahkan tidak tahu apakah ada yang membacanya. Pada 2016 itu diusulkan lagi di N2023 , dan saya mendorong siapa pun yang tahu bagaimana proposal itu akan memberi tahu kami di komentar.


2
Dilihat oleh ketidakmampuan saya untuk menemukan solusi di web dan kurangnya jawaban di sini, saya kira Anda benar):
jwd

2
Apakah n976 yang Anda maksud? Aku mencari sisa kelompok C kerja 's dokumen untuk respon tetapi tidak pernah menemukan satu. Bahkan tidak ada dalam agenda untuk pertemuan berikutnya . Satu-satunya hit lain pada topik ini adalah komentar Norwegia # 4 di n868 sejak sebelum C99 disahkan (lagi-lagi tanpa diskusi lanjutan).
Richard Hansen

4
Ya, khususnya paruh kedua itu. Mungkin ada diskusi comp.std.ctetapi saya tidak dapat menemukannya di Google Groups sekarang; tentu saja tidak pernah mendapat perhatian dari komite yang sebenarnya (atau jika itu terjadi, tidak ada yang pernah memberi tahu saya tentang itu).
zwol

1
Saya khawatir saya tidak punya bukti, saya juga bukan orang yang tepat untuk mencoba memikirkannya. Saya memang menulis setengah dari preprosesor GCC, tapi itu lebih dari sepuluh tahun yang lalu, dan saya tidak pernah memikirkan trik penghitungan argumen di bawah, bahkan saat itu.
zwol

6
Ekstensi ini berfungsi dengan kompilasi clang & intel icc, serta gcc.
ACyclic

112

Ada trik penghitungan argumen yang bisa Anda gunakan.

Berikut adalah satu cara yang sesuai standar untuk menerapkan BAR()contoh kedua dalam pertanyaan 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;
}

Trik yang sama ini digunakan untuk:

Penjelasan

Strateginya adalah untuk memisahkan __VA_ARGS__ke dalam argumen pertama dan sisanya (jika ada). Ini memungkinkan untuk memasukkan barang setelah argumen pertama tetapi sebelum argumen kedua (jika ada).

FIRST()

Makro ini hanya memperluas argumen pertama, membuang sisanya.

Implementasinya sangat mudah. The throwawayArgumen memastikan bahwa FIRST_HELPER()mendapat dua argumen, yang diperlukan karena ...kebutuhan minimal satu. Dengan satu argumen, itu berkembang sebagai berikut:

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

Dengan dua atau lebih, itu mengembang sebagai berikut:

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

REST()

Makro ini mengembang ke segala sesuatu kecuali argumen pertama (termasuk koma setelah argumen pertama, jika ada lebih dari satu argumen).

Implementasi makro ini jauh lebih rumit. Strategi umum adalah menghitung jumlah argumen (satu atau lebih dari satu) dan kemudian berkembang menjadi REST_HELPER_ONE()(jika hanya satu argumen yang diberikan) atau REST_HELPER_TWOORMORE()(jika dua atau lebih argumen diberikan). REST_HELPER_ONE()cukup mengembang menjadi tidak ada - tidak ada argumen setelah yang pertama, jadi argumen yang tersisa adalah set kosong. REST_HELPER_TWOORMORE()juga mudah - itu mengembang ke koma diikuti oleh segala sesuatu kecuali argumen pertama.

Argumen dihitung menggunakan NUM()makro. Makro ini diperluas ke ONEjika hanya satu argumen yang diberikan, TWOORMOREjika antara dua dan sembilan argumen diberikan, dan istirahat jika 10 atau lebih argumen diberikan (karena diperluas ke argumen 10).

The NUM()makro menggunakan SELECT_10TH()makro untuk menentukan jumlah argumen. Seperti namanya, SELECT_10TH()cukup memperluas argumen ke-10. Karena ellipsis, SELECT_10TH()perlu diberikan setidaknya 11 argumen (standar mengatakan bahwa harus ada setidaknya satu argumen untuk ellipsis). Inilah sebabnya mengapa NUM()lewat throwawaysebagai argumen terakhir (tanpa itu, melewati satu argumen untuk NUM()akan menghasilkan hanya 10 argumen yang diteruskan keSELECT_10TH() , yang akan melanggar standar).

Pemilihan salah satu REST_HELPER_ONE()atau REST_HELPER_TWOORMORE()dilakukan dengan menggabungkan REST_HELPER_dengan ekspansi NUM(__VA_ARGS__)di REST_HELPER2(). Perhatikan bahwa tujuannya REST_HELPER()adalah untuk memastikan bahwa NUM(__VA_ARGS__)sepenuhnya diperluas sebelum digabungkan dengan REST_HELPER_.

Ekspansi dengan satu argumen berbunyi sebagai berikut:

  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. (kosong)

Ekspansi dengan dua atau lebih argumen berjalan sebagai berikut:

  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
Perhatikan bahwa ini akan gagal jika Anda memanggil BAR dengan 10 atau lebih argumen, dan meskipun relatif mudah untuk memperluas ke lebih banyak argumen, itu akan selalu memiliki batas atas jumlah argumen yang dapat ditangani
Chris Dodd

2
@ ChrisDodd: Benar. Sayangnya, tampaknya tidak ada cara untuk menghindari batasan jumlah argumen tanpa mengandalkan ekstensi spesifik-kompiler. Juga, saya tidak mengetahui cara untuk menguji andal jika ada terlalu banyak argumen (sehingga pesan kesalahan kompiler yang berguna dapat dicetak, daripada kegagalan yang aneh).
Richard Hansen

17

Bukan solusi umum, tetapi dalam hal printf Anda dapat menambahkan baris baru seperti:

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

Saya percaya ini mengabaikan argumen tambahan yang tidak dirujuk dalam format string. Jadi Anda bahkan mungkin bisa lolos dengan:

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

Saya tidak percaya C99 disetujui tanpa cara standar untuk melakukan ini. AFAICT masalah ada di C ++ 11 juga.


masalah dengan 0 ekstra ini adalah bahwa ia benar-benar akan berakhir dalam kode jika ia memanggil fungsi vararg. Periksa solusi yang disediakan oleh Richard Hansen
Pavel P

@Pavel benar tentang contoh kedua, tetapi yang pertama berfungsi dengan baik. +1.
kirbyfan64sos

11

Ada cara untuk menangani kasus spesifik ini menggunakan sesuatu seperti Boost.Prosesor . Anda dapat menggunakan BOOST_PP_VARIADIC_SIZE untuk memeriksa ukuran daftar argumen, dan kemudian memperluas ke makro lain. Satu kekurangannya adalah tidak bisa membedakan antara argumen 0 dan 1, dan alasannya menjadi jelas setelah Anda mempertimbangkan yang berikut ini:

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

Daftar argumen makro kosong sebenarnya terdiri dari satu argumen yang kebetulan kosong.

Dalam hal ini, kami beruntung karena makro yang Anda inginkan selalu memiliki setidaknya 1 argumen, kami dapat mengimplementasikannya sebagai dua makro "kelebihan":

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

Dan kemudian makro lain untuk beralih di antara mereka, seperti:

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

atau

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

Mana pun yang Anda temukan lebih mudah dibaca (saya lebih suka yang pertama karena memberi Anda bentuk umum untuk memuat makro pada jumlah argumen).

Dimungkinkan juga untuk melakukan ini dengan makro tunggal dengan mengakses dan mengubah daftar argumen variabel, tetapi cara ini kurang dapat dibaca, dan sangat spesifik untuk masalah ini:

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

Juga, mengapa tidak ada BOOST_PP_ARRAY_ENUM_TRAILING? Itu akan membuat solusi ini tidak terlalu mengerikan.

Sunting: Baiklah, ini adalah BOOST_PP_ARRAY_ENUM_TRAILING, dan versi yang menggunakannya (ini sekarang solusi favorit saya):

#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
Senang mempelajari tentang Boost.Prosesor, +1. Catatan yang BOOST_PP_VARIADIC_SIZE()menggunakan trik penghitungan argumen yang sama yang saya dokumentasikan dalam jawaban saya, dan memiliki batasan yang sama (itu akan pecah jika Anda melewati lebih dari sejumlah argumen).
Richard Hansen

1
Yap, saya melihat bahwa pendekatan Anda sama dengan yang digunakan oleh Boost, tetapi solusi peningkatannya dipertahankan dengan sangat baik, dan memiliki banyak fitur lain yang sangat berguna untuk digunakan saat mengembangkan makro yang lebih canggih. Hal-hal rekursi sangat keren (dan digunakan di belakang layar dalam pendekatan terakhir yang menggunakan BOOST_PP_ARRAY_ENUM).
DRayX

1
Jawaban Boost yang sebenarnya berlaku untuk tag c ! Hore!
Justin

6

Makro yang sangat sederhana yang saya gunakan untuk mencetak 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;
}

Tidak peduli berapa banyak argumen yang diteruskan ke DBG, tidak ada peringatan c99.

Caranya adalah __DBG_INTmenambahkan param dummy sehingga ...akan selalu memiliki setidaknya satu argumen dan c99 puas.


5

Saya mengalami masalah yang sama baru-baru ini, dan saya yakin ada solusi.

Ide kuncinya adalah bahwa ada cara untuk menulis makro NUM_ARGSuntuk menghitung jumlah argumen yang diberikan makro variadic. Anda bisa menggunakan variasi NUM_ARGSuntuk membangun NUM_ARGS_CEILING2, yang dapat memberi tahu Anda apakah makro variadik diberikan 1 argumen atau 2 atau lebih argumen. Kemudian Anda bisa menulis Barmakro Anda sehingga menggunakan NUM_ARGS_CEILING2danCONCAT mengirim argumennya ke salah satu dari dua makro pembantu: satu yang mengharapkan tepat 1 argumen, dan yang lain mengharapkan sejumlah variabel argumen lebih besar dari 1.

Berikut adalah contoh di mana saya menggunakan trik ini untuk menulis makro UNIMPLEMENTED, yang sangat mirip dengan BAR:

LANGKAH 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

LANGKAH 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)

Langkah 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__)

LANGKAH 3:

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

Di mana CONCAT diimplementasikan dengan cara biasa. Sebagai petunjuk singkat, jika hal di atas tampak membingungkan: tujuan CONCAT ada untuk memperluas ke "panggilan" makro lainnya.

Perhatikan bahwa NUM_ARGS sendiri tidak digunakan. Saya hanya memasukkannya untuk menggambarkan trik dasar di sini. Lihatlah blog Pens Jens Gustedt untuk mendapatkan perawatan yang bagus.

Dua catatan:

  • NUM_ARGS terbatas dalam jumlah argumen yang ditangani. Milik saya hanya dapat menangani hingga 20, meskipun jumlahnya benar-benar sewenang-wenang.

  • NUM_ARGS, seperti yang ditunjukkan, memiliki jebakan karena ia mengembalikan 1 ketika diberikan 0 argumen. Intinya adalah bahwa NUM_ARGS secara teknis menghitung [koma + 1], dan bukan argumen. Dalam kasus khusus ini, ini benar-benar bermanfaat bagi kita. _UNIMPLEMENTED1 akan menangani token kosong dengan baik dan itu menyelamatkan kita dari keharusan menulis _UNIMPLEMENTED0. Gustedt memiliki solusi untuk itu juga, meskipun saya belum menggunakannya dan saya tidak yakin apakah itu akan berhasil untuk apa yang kami lakukan di sini.


+1 untuk memunculkan trik penghitungan argumen, -1 karena benar-benar sulit diikuti
Richard Hansen

Komentar yang Anda tambahkan adalah peningkatan, tetapi masih ada sejumlah masalah: 1. Anda berdiskusi dan mendefinisikan NUM_ARGStetapi tidak menggunakannya. 2. Apa tujuan dari UNIMPLEMENTED? 3. Anda tidak pernah memecahkan contoh masalah dalam pertanyaan. 4. Berjalan melalui ekspansi satu langkah pada satu waktu akan menggambarkan cara kerjanya dan menjelaskan peran setiap makro pembantu. 5. Membahas 0 argumen mengganggu; OP bertanya tentang kepatuhan standar, dan 0 argumen dilarang (C99 6.10.3p4). 6. Langkah 1.5? Kenapa tidak langkah 2? 7. "Langkah" menyiratkan tindakan yang terjadi secara berurutan; ini hanya kode.
Richard Hansen

8. Anda menautkan ke seluruh blog, bukan pos yang relevan. Saya tidak dapat menemukan pos yang Anda maksud. 9. Paragraf terakhir canggung: Metode ini tidak jelas; itu sebabnya tidak ada orang lain yang memposting solusi yang benar sebelumnya. Juga, jika itu berfungsi dan mematuhi standar, jawaban Zack pasti salah. 10. Anda harus mendefinisikan CONCAT()- jangan menganggap pembaca tahu cara kerjanya.
Richard Hansen

(Tolong jangan menafsirkan umpan balik ini sebagai serangan - saya benar-benar ingin meningkatkan jawaban Anda tetapi tidak merasa nyaman melakukannya kecuali itu dibuat lebih mudah untuk dipahami. Jika Anda dapat meningkatkan kejelasan jawaban Anda, saya akan tingkatkan milikmu dan hapus milikku.)
Richard Hansen

2
Saya tidak akan pernah memikirkan pendekatan ini, dan saya menulis kira-kira setengah dari preprosesor GCC saat ini! Yang mengatakan, saya masih mengatakan bahwa "tidak ada cara standar untuk mendapatkan efek ini" karena kedua teknik Anda dan Richard memaksakan batas atas jumlah argumen ke makro.
zwol

2

Ini adalah versi sederhana yang saya gunakan. Itu didasarkan pada teknik-teknik hebat dari jawaban-jawaban lain di sini, begitu banyak alat bantu untuk mereka:

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

Itu dia.

Seperti dengan solusi lain ini terbatas pada jumlah argumen makro. Untuk mendukung lebih banyak, tambahkan lebih banyak parameter _SELECT, dan lebih banyak Nargumen. Nama argumen menghitung mundur (bukan naik) untuk berfungsi sebagai pengingat bahwa SUFFIXargumen berbasis hitungan disediakan dalam urutan terbalik.

Solusi ini memperlakukan 0 argumen seolah-olah itu adalah 1 argumen. Jadi BAR()secara nominal "bekerja", karena ia mengembang ke _SELECT(_BAR,,N,N,N,N,1)(), yang mengembang ke _BAR_1()(), yang mengembang ke printf("\n").

Jika mau, Anda bisa berkreasi dengan menggunakan _SELECTdan menyediakan makro yang berbeda untuk jumlah argumen yang berbeda. Sebagai contoh, di sini kita memiliki makro LOG yang mengambil argumen 'level' sebelum format. Jika format tidak ada, ia mencatat "(tidak ada pesan)", jika hanya ada 1 argumen, ia akan mencatatnya melalui "% s", jika tidak maka akan memperlakukan argumen format sebagai string format printf untuk argumen yang tersisa.

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

Ini masih memicu peringatan saat dikompilasi dengan -pedantic.
PSkocik

1

Dalam situasi Anda (setidaknya 1 argumen hadir, tidak pernah 0), Anda dapat mendefinisikan BARsebagai BAR(...), gunakan Jens Gustedt HAS_COMMA(...) untuk mendeteksi koma dan kemudian mengirim ke BAR0(Fmt)atau BAR1(Fmt,...)sesuai.

Ini:

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

kompilasi dengan -pedantictanpa peringatan.


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

Cobalah online!

Mengasumsikan:

  • Tidak ada arg yang mengandung koma atau braket
  • Tanpa arg berisi A~ G(bisa mengganti nama menjadi hard_collide)

The no arg contain commapembatasan dapat dilewati dengan memeriksa multi-setelah beberapa melewati lebih, tapi no bracketmasih ada
l4m2

-2

Solusi standar adalah menggunakan FOOalih-alih BAR. Ada beberapa kasus aneh dalam menyusun ulang argumen yang mungkin tidak bisa dilakukan untuk Anda (walaupun saya yakin seseorang dapat membuat hacks cerdas untuk membongkar dan memasang kembali secara __VA_ARGS__kondisional berdasarkan jumlah argumen di dalamnya!) Tetapi secara umum menggunakan FOO"biasanya" hanya bekerja.


1
Pertanyaannya adalah "adakah cara yang sesuai standar untuk mendapatkan perilaku ini?"
Marsh Ray

2
Dan pertanyaannya termasuk alasan untuk tidak menggunakan FOO sejak lama.
Pavel Šimerda
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.