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 fmt
selalu 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.
BAR
daripadaFOO
di tempat pertama?__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)Jawaban:
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.sumber
comp.std.c
tetapi 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).Ada trik penghitungan argumen yang bisa Anda gunakan.
Berikut adalah satu cara yang sesuai standar untuk menerapkan
BAR()
contoh kedua dalam pertanyaan jwd:Trik yang sama ini digunakan untuk:
__VA_ARGS__
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
throwaway
Argumen memastikan bahwaFIRST_HELPER()
mendapat dua argumen, yang diperlukan karena...
kebutuhan minimal satu. Dengan satu argumen, itu berkembang sebagai berikut:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Dengan dua atau lebih, itu mengembang sebagai berikut:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
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) atauREST_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 keONE
jika hanya satu argumen yang diberikan,TWOORMORE
jika antara dua dan sembilan argumen diberikan, dan istirahat jika 10 atau lebih argumen diberikan (karena diperluas ke argumen 10).The
NUM()
makro menggunakanSELECT_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 mengapaNUM()
lewatthrowaway
sebagai argumen terakhir (tanpa itu, melewati satu argumen untukNUM()
akan menghasilkan hanya 10 argumen yang diteruskan keSELECT_10TH()
, yang akan melanggar standar).Pemilihan salah satu
REST_HELPER_ONE()
atauREST_HELPER_TWOORMORE()
dilakukan dengan menggabungkanREST_HELPER_
dengan ekspansiNUM(__VA_ARGS__)
diREST_HELPER2()
. Perhatikan bahwa tujuannyaREST_HELPER()
adalah untuk memastikan bahwaNUM(__VA_ARGS__)
sepenuhnya diperluas sebelum digabungkan denganREST_HELPER_
.Ekspansi dengan satu argumen berbunyi sebagai berikut:
REST(firstarg)
REST_HELPER(NUM(firstarg), firstarg)
REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
REST_HELPER2(ONE, firstarg)
REST_HELPER_ONE(firstarg)
Ekspansi dengan dua atau lebih argumen berjalan sebagai berikut:
REST(firstarg, secondarg, thirdarg)
REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
, secondarg, thirdarg
sumber
Bukan solusi umum, tetapi dalam hal printf Anda dapat menambahkan baris baru seperti:
Saya percaya ini mengabaikan argumen tambahan yang tidak dirujuk dalam format string. Jadi Anda bahkan mungkin bisa lolos dengan:
Saya tidak percaya C99 disetujui tanpa cara standar untuk melakukan ini. AFAICT masalah ada di C ++ 11 juga.
sumber
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:
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":
Dan kemudian makro lain untuk beralih di antara mereka, seperti:
atau
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:
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):
sumber
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).Makro yang sangat sederhana yang saya gunakan untuk mencetak debug:
Tidak peduli berapa banyak argumen yang diteruskan ke DBG, tidak ada peringatan c99.
Caranya adalah
__DBG_INT
menambahkan param dummy sehingga...
akan selalu memiliki setidaknya satu argumen dan c99 puas.sumber
Saya mengalami masalah yang sama baru-baru ini, dan saya yakin ada solusi.
Ide kuncinya adalah bahwa ada cara untuk menulis makro
NUM_ARGS
untuk menghitung jumlah argumen yang diberikan makro variadic. Anda bisa menggunakan variasiNUM_ARGS
untuk membangunNUM_ARGS_CEILING2
, yang dapat memberi tahu Anda apakah makro variadik diberikan 1 argumen atau 2 atau lebih argumen. Kemudian Anda bisa menulisBar
makro Anda sehingga menggunakanNUM_ARGS_CEILING2
danCONCAT
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 denganBAR
:LANGKAH 1:
LANGKAH 1.5:
Langkah 2:
LANGKAH 3:
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.
sumber
NUM_ARGS
tetapi tidak menggunakannya. 2. Apa tujuan dariUNIMPLEMENTED
? 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.CONCAT()
- jangan menganggap pembaca tahu cara kerjanya.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:
Itu dia.
Seperti dengan solusi lain ini terbatas pada jumlah argumen makro. Untuk mendukung lebih banyak, tambahkan lebih banyak parameter
_SELECT
, dan lebih banyakN
argumen. Nama argumen menghitung mundur (bukan naik) untuk berfungsi sebagai pengingat bahwaSUFFIX
argumen 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 keprintf("\n")
.Jika mau, Anda bisa berkreasi dengan menggunakan
_SELECT
dan 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.sumber
Dalam situasi Anda (setidaknya 1 argumen hadir, tidak pernah 0), Anda dapat mendefinisikan
BAR
sebagaiBAR(...)
, gunakan Jens GustedtHAS_COMMA(...)
untuk mendeteksi koma dan kemudian mengirim keBAR0(Fmt)
atauBAR1(Fmt,...)
sesuai.Ini:
kompilasi dengan
-pedantic
tanpa peringatan.sumber
C (gcc) , 762 byte
Cobalah online!
Mengasumsikan:
A
~G
(bisa mengganti nama menjadi hard_collide)sumber
no arg contain comma
pembatasan dapat dilewati dengan memeriksa multi-setelah beberapa melewati lebih, tapino bracket
masih adaSolusi standar adalah menggunakan
FOO
alih-alihBAR
. 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 menggunakanFOO
"biasanya" hanya bekerja.sumber