C ++ preprocessor __VA_ARGS__ jumlah argumen


99

Pertanyaan sederhana yang tidak dapat saya temukan jawabannya di internet. Dalam makro argumen variadic, bagaimana menemukan jumlah argumen? Saya setuju dengan boost preprocessor, jika ada solusinya.

Jika itu membuat perbedaan, saya mencoba untuk mengubah jumlah variabel argumen makro untuk meningkatkan urutan, daftar, atau larik preprocessor untuk pemrosesan ulang lebih lanjut.


Hanya untuk memperjelas - Anda bertanya tentang makro variadic, dan bukan makro yang digunakan untuk membuat fungsi C variadic?

2
apakah argumen jenisnya sama? jika demikian, dan jika tipenya diketahui, ada larutan C standar melalui literal majemuk; jika tidak diketahui, Anda dapat menggunakannya __typeof__untuk membuatnya berfungsi setidaknya pada beberapa kompiler
Christoph

1
Karena pembahasannya adalah tentang Boost preprocessor sequence dll, maka harus C ++ (itulah sebabnya saya memberi tag ulang pada Q - tetapi gagal mengubah judul pertanyaan) ... Ups; Saya akan memperbaikinya.
Jonathan Leffler

@JonathanLeffler Benar, Boost adalah pustaka C ++. Namun, Boost.Preprocessor dapat digunakan dengan C. AFAIK, tidak ada yang menggunakan C ++ spesifik.
Justin

Jawaban:


90

Ini sebenarnya bergantung pada kompilator, dan tidak didukung oleh standar apa pun.

Namun di sini Anda memiliki penerapan makro yang menghitung:

#define PP_NARG(...) \
         PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
         PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0

/* Some test cases */


PP_NARG(A) -> 1
PP_NARG(A,B) -> 2
PP_NARG(A,B,C) -> 3
PP_NARG(A,B,C,D) -> 4
PP_NARG(A,B,C,D,E) -> 5
PP_NARG(1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3,4,5,6,7,8,9,0,
         1,2,3) -> 63

.... tapi sekarang standar dalam C ++ 0x dan seharusnya sudah lebih lama karena memungkinkan cara yang bagus untuk menjaga fungsi varadic dari panggilan yang rusak (yaitu, Anda dapat meneruskan nilai setelah item varadic. Ini sebenarnya cara mendapatkan hitungan saya digunakan untuk digunakan, tetapi saya kira sizeof bisa bekerja terlalu ..
osirisgothra

Jawabannya tertaut ke situs lain. Juga tautannya sepertinya tidak mengarah ke jawaban yang benar. Dan bahkan jika saya berhasil menemukan jawaban yang dimaksud, itu tampak buruk karena menyematkan "-1" hardcode yang akan dikompilasi. Ada metode yang lebih baik.
ceztko

2
Terima kasih! ini bekerja di Visual Studio 2013 untuk saya: #define EXPAND(x) x #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) N #define PP_NARG(...) EXPAND(PP_ARG_N(__VA_ARGS__, 9,8,7,6,5,4,3,2,1,0))``
mchiasson

1
PP_NARG()gagal mengembalikan 0. Solusi GET_ARG_COUNT()& Y_TUPLE_SIZE()berfungsi.
PSkocik

1
" PP_NARG()Gagal untuk kembali 0" ... belum tentu masalah. Seseorang dapat mengatakan bahwa PP_NARG() harus mengembalikan 1 untuk alasan yang sama PP_NARG(,)harus mengembalikan 2. Mendeteksi 0 mungkin memang berguna dalam beberapa kasus, tetapi solusinya tampaknya kurang umum (mengharuskan token pertama itu dapat ditempel; yang mungkin atau mungkin tidak baik-baik saja tergantung untuk apa Anda menggunakannya), atau khusus implementasi (seperti membutuhkan trik gnu-menghapus-tempelkan koma).
H Walters

100

Saya biasanya menggunakan makro ini untuk menemukan sejumlah parameter:

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

Contoh lengkap:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#define SUM(...)  (sum(NUMARGS(__VA_ARGS__), __VA_ARGS__))

void sum(int numargs, ...);

int main(int argc, char *argv[]) {

    SUM(1);
    SUM(1, 2);
    SUM(1, 2, 3);
    SUM(1, 2, 3, 4);

    return 1;
}

void sum(int numargs, ...) {
    int     total = 0;
    va_list ap;

    printf("sum() called with %d params:", numargs);
    va_start(ap, numargs);
    while (numargs--)
        total += va_arg(ap, int);
    va_end(ap);

    printf(" %d\n", total);

    return;
}

Ini adalah kode C99 yang sepenuhnya valid. Ini memiliki satu kelemahan, meskipun - Anda tidak dapat menjalankan makro SUM()tanpa params, tetapi GCC memiliki solusi untuk itu - lihat di sini .

Jadi dalam kasus GCC, Anda perlu menentukan makro seperti ini:

#define       NUMARGS(...)  (sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1)
#define       SUM(...)  sum(NUMARGS(__VA_ARGS__), ##__VA_ARGS__)

dan itu akan bekerja bahkan dengan daftar parameter kosong


4
UM, itu tidak akan berfungsi untuk OP, dia membutuhkan ukuran untuk BOOST_PP yang berjalan pada waktu kompilasi.
Kornel Kisielewicz

5
Pintar! Apakah itu juga berfungsi kapan sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel Seperti makro lainnya, makro dievaluasi pada waktu kompilasi. Saya tidak tahu tentang Boost, tetapi Boost tidak diperlukan.
qrdl

4
@Adam Karena saya melakukan cast {__VA_ARGS__}ke int[], itu adil int[], terlepas dari konten sebenarnya__VA_ARGS__
qrdl

3
Solusi yang elegan! Bekerja di VS2017. Tidak ##diperlukan di VS2017 karena kosong __VA_ARGS__akan secara otomatis menghapus koma sebelumnya.
poby

37

Jika Anda menggunakan C ++ 11, dan Anda memerlukan nilai sebagai konstanta waktu kompilasi C ++, solusi yang sangat elegan adalah ini:

#include <tuple>

#define MACRO(...) \
    std::cout << "num args: " \
    << std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value \
    << std::endl;

Harap diperhatikan: penghitungan terjadi seluruhnya pada waktu kompilasi, dan nilainya dapat digunakan setiap kali integer waktu kompilasi diperlukan, misalnya sebagai parameter template untuk std :: array.


2
Solusi bagus! Dan tidak seperti yang sizeof((int[]){__VA_ARGS__})/sizeof(int)disarankan di atas, ini berfungsi bahkan ketika semua argumen tidak dapat diterapkan int.
Wim

Sepakat. Solusi bagus! ++.
davernator

Tidak bekerja dengan template, yaitu NUMARGS (jumlah <1,2>); lihat godbolt.org/z/_AAxmL
jorgbrown

1
Saya ... berpikir itu mungkin benar-benar poin yang mendukungnya, @jorgbrown, setidaknya dalam banyak kasus di mana itu akan muncul. Karena ini bergantung pada kompiler daripada preprocessor untuk melakukan penghitungan, ini memberikan jumlah argumen seperti yang terlihat oleh kompilator, yang kemungkinan akan cocok dengan apa yang diharapkan kebanyakan programmer. Ini akan menyebabkan masalah jika Anda mengharapkannya untuk mempertimbangkan keserakahan preprocessor.
Waktu Justin - Kembalikan Monica

Jawaban yang luar biasa. Anda dapat memasukkannya ke dalam makro#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

Untuk kenyamanan, berikut adalah implementasi yang berfungsi untuk 0 hingga 70 argumen, dan berfungsi di Visual Studio, GCC, dan Clang . Saya yakin ini akan berfungsi di Visual Studio 2010 dan yang lebih baru, tetapi hanya mengujinya di VS2013.

#ifdef _MSC_VER // Microsoft compilers

#   define GET_ARG_COUNT(...)  INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))

#   define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#   define INTERNAL_EXPAND(x) x
#   define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#else // Non-Microsoft compilers

#   define GET_ARG_COUNT(...) INTERNAL_GET_ARG_COUNT_PRIVATE(0, ## __VA_ARGS__, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#   define INTERNAL_GET_ARG_COUNT_PRIVATE(_0, _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#endif

static_assert(GET_ARG_COUNT() == 0, "GET_ARG_COUNT() failed for 0 arguments");
static_assert(GET_ARG_COUNT(1) == 1, "GET_ARG_COUNT() failed for 1 argument");
static_assert(GET_ARG_COUNT(1,2) == 2, "GET_ARG_COUNT() failed for 2 arguments");
static_assert(GET_ARG_COUNT(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70) == 70, "GET_ARG_COUNT() failed for 70 arguments");

IMHO varian Microsoft gagal untuk argumen nol.
Vroomfondel

@Vroomfondel varian Microsoft berfungsi untuk nol argumen. Static_assert pertama dalam contoh di atas adalah tes khusus untuk kasus nol-argumen, dan saya baru saja menyusun dan menjalankannya di Visual Studio 2017 v15.8.9.
Chris Kline

Menarik - menggunakan varian Microsoft pada kompiler non-Microsoft tidak berfungsi - apakah Anda tahu apa yang dilakukan preprocessor M $ secara berbeda yang membuat kode bekerja sebaliknya? BTW Saya mencoba C, bukan C ++;
Vroomfondel

Saya percaya itu karena MSVC sedikit lebih baik tentang "panjang-nol __VA_ARGS__" (yang dalam C ++, secara teknis merupakan ekstensi kompilator (standar de facto yang hampir universal ) hingga C ++ 20). Sebagian besar (? Semua) kompiler memungkinkan nol-panjang, tapi tersedak pada trailing koma jika daftar adalah kosong (dan kelebihan ##sebagai proto sebuah __VA_OPT__, untuk menghapus koma dalam kasus ini); Versi ekstensi MSVC tidak hanya mencekik koma (tetapi akan tersedak jika kelebihan beban ##). Bandingkan MSVC unused, __VA_ARGS__dengan non-MSVC 0, ## __VA_ARGS__; tidak ada yang lebih tepat, masalahnya adalah mereka berbeda.
Waktu Justin - Kembalikan Monica

Saya tidak yakin apakah ini sama di C, meskipun, @Vroomfondel, karena saya kehilangan bookmark saya ke draf terbaru.
Waktu Justin - Kembalikan Monica

11

Ada beberapa solusi C ++ 11 untuk menemukan jumlah argumen pada waktu kompilasi, tetapi saya terkejut melihat bahwa tidak ada yang menyarankan sesuatu yang sederhana seperti:

#define VA_COUNT(...) detail::va_count(__VA_ARGS__)

namespace detail
{
    template<typename ...Args>
    constexpr std::size_t va_count(Args&&...) { return sizeof...(Args); }
}

Ini juga tidak memerlukan penyertaan <tuple>header.


1
"tapi kenapa tidak menggunakan template variadic dan sizeof ... sebagai gantinya (seperti dalam jawaban saya sendiri)" c ++ telah menjadi monster. Ini memiliki terlalu banyak fitur dan banyak di antaranya, seperti template variadic, jarang digunakan. Anda membacanya, Anda menulis beberapa contoh dan kemudian Anda melupakannya. Oleh karena itu, sulit untuk mendapatkan ide yang tepat pada waktu yang tepat. Karena solusi Anda tampaknya menjadi pilihan yang lebih baik daripada solusi saya, saya akan membiarkan seleksi alam bekerja dan saya akan menghapus solusi saya.
zdf

1
@ZDF dapat dimengerti, tetapi saya kebetulan menggunakan template variadic terus-menerus. Program saya menjadi jauh lebih kuat sejak C ++ 11, dan ini adalah salah satu alasan utama mengapa. Tidak perlu menghapus jawaban Anda, saya kira.
monkey0506

1
Ini tidak akan berhasil dengan orang seperti VA_COUNT(&,^,%). Juga, jika Anda menghitung melalui funtion, saya tidak melihat ada gunanya membuat makro.
Qwertiy

Solusi ini tetap menjadi pertanyaan: parameter VA_COUNT adalah semua pengidentifikasi yang belum didefinisikan sebagai variabel atau sesuatu, dan itu menyebabkan kesalahan 'variabel *** tidak ditentukan'. Apakah ada cara untuk memperbaikinya?
ipid

7

ini berfungsi dengan 0 argumen dengan gcc / llvm. [tautan bodoh]

/*
 * we need a comma at the start for ##_VA_ARGS__ to consume then
 * the arguments are pushed out in such a way that 'cnt' ends up with
 * the right count.  
 */
#define COUNT_ARGS(...) COUNT_ARGS_(,##__VA_ARGS__,6,5,4,3,2,1,0)
#define COUNT_ARGS_(z,a,b,c,d,e,f,cnt,...) cnt

#define C_ASSERT(test) \
    switch(0) {\
      case 0:\
      case test:;\
    }

int main() {
   C_ASSERT(0 ==  COUNT_ARGS());
   C_ASSERT(1 ==  COUNT_ARGS(a));
   C_ASSERT(2 ==  COUNT_ARGS(a,b));
   C_ASSERT(3 ==  COUNT_ARGS(a,b,c));
   C_ASSERT(4 ==  COUNT_ARGS(a,b,c,d));
   C_ASSERT(5 ==  COUNT_ARGS(a,b,c,d,e));
   C_ASSERT(6 ==  COUNT_ARGS(a,b,c,d,e,f));
   return 0;
}

Visual Studio tampaknya mengabaikan operator ## yang digunakan untuk menggunakan argumen kosong. Anda mungkin bisa menyiasatinya dengan sesuatu seperti

#define CNT_ COUNT_ARGS
#define PASTE(x,y) PASTE_(x,y)
#define PASTE_(x,y) x ## y
#define CNT(...) PASTE(ARGVS,PASTE(CNT_(__VA_ARGS__),CNT_(1,##__VA_ARGS__)))
//you know its 0 if its 11 or 01
#define ARGVS11 0
#define ARGVS01 0
#define ARGVS12 1
#define ARGVS23 2
#define ARGVS34 3

Saya menguji ini untuk Visual Studio 2008 dan tidak berhasil untuk 0 argumen COUNT_ARGS () = 1.
user720594

Tautannya sepertinya rusak.
Jan Smrčina

tautan tetap. VS pasti melakukan sesuatu yang berbeda seperti biasanya :). Saya tidak berpikir mereka akan mendukung C99 sepenuhnya dalam waktu dekat.
pengguna1187902

2
Eh, ##__VA_ARGS__memakan koma sebelumnya jika __VA_ARGS__kosong adalah ekstensi GCC. Ini bukan perilaku standar.
Gugatan Dana Monica

6

Dengan ekstensi D3D:

#define Y_TUPLE_SIZE(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args

#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

Bekerja untuk 0 - 32 argumen. Batas ini dapat dengan mudah diperpanjang.

EDIT: Versi sederhana (bekerja di VS2015 14.0.25431.01 Pembaruan 3 & gcc 7.4.0) hingga 100 argumen untuk disalin & ditempel:

#define COUNTOF(...) _COUNTOF_CAT( _COUNTOF_A, ( 0, ##__VA_ARGS__, 100,\
    99, 98, 97, 96, 95, 94, 93, 92, 91, 90,\
    89, 88, 87, 86, 85, 84, 83, 82, 81, 80,\
    79, 78, 77, 76, 75, 74, 73, 72, 71, 70,\
    69, 68, 67, 66, 65, 64, 63, 62, 61, 60,\
    59, 58, 57, 56, 55, 54, 53, 52, 51, 50,\
    49, 48, 47, 46, 45, 44, 43, 42, 41, 40,\
    39, 38, 37, 36, 35, 34, 33, 32, 31, 30,\
    29, 28, 27, 26, 25, 24, 23, 22, 21, 20,\
    19, 18, 17, 16, 15, 14, 13, 12, 11, 10,\
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ) )
#define _COUNTOF_CAT( a, b ) a b
#define _COUNTOF_A( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9,\
    a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,\
    a20, a21, a22, a23, a24, a25, a26, a27, a28, a29,\
    a30, a31, a32, a33, a34, a35, a36, a37, a38, a39,\
    a40, a41, a42, a43, a44, a45, a46, a47, a48, a49,\
    a50, a51, a52, a53, a54, a55, a56, a57, a58, a59,\
    a60, a61, a62, a63, a64, a65, a66, a67, a68, a69,\
    a70, a71, a72, a73, a74, a75, a76, a77, a78, a79,\
    a80, a81, a82, a83, a84, a85, a86, a87, a88, a89,\
    a90, a91, a92, a93, a94, a95, a96, a97, a98, a99,\
    a100, n, ... ) n

4
apakah hanya saya atau apakah ini semacam melanggar aturan bau kode ..?
osirisgothra

Ini bekerja untuk saya dengan VC ++ hingga setidaknya VS2012, dan GCC dan clang juga dalam pengujian dasar saya.
ThreeBit

@osirisgothra, kenapa baunya?
ceztko

Meskipun makro ini memiliki dukungan kompiler yang luas, makro ini tidak berfungsi dengan argumen makro seperti string, seperti Y_TUPLE_SIZE("Hello"), membuatnya sangat tidak layak. Saya setuju dengan @osirisgothra.
ceztko

1
Makro ini dapat bekerja untuk Anda tetapi memiliki kerusakan yang serius. Saya melakukan banyak penelitian dan menemukan pendekatan yang lebih bersih yang berfungsi di GCC dan VS. Anda dapat menemukannya dalam jawaban saya untuk pertanyaan serupa.
ceztko

3

Saya berasumsi bahwa setiap argumen ke VA_ARGS akan dipisahkan dengan koma. Jika demikian, saya pikir ini seharusnya berfungsi sebagai cara yang cukup bersih untuk melakukan ini.

#include <cstring>

constexpr int CountOccurances(const char* str, char c) {
    return str[0] == char(0) ? 0 : (str[0] == c) + CountOccurances(str+1, c);
}

#define NUMARGS(...) (CountOccurances(#__VA_ARGS__, ',') + 1)

int main(){
    static_assert(NUMARGS(hello, world) == 2, ":(")  ;
    return 0;
}

Bekerja untuk saya di godbolt untuk clang 4 dan GCC 5.1. Ini akan menghitung pada waktu kompilasi, tetapi tidak akan mengevaluasi preprocessor. Jadi jika Anda mencoba melakukan sesuatu seperti membuat FOR_EACH , ini tidak akan berhasil.


Jawaban ini diremehkan. Ini akan bekerja bahkan untuk NUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! Setiap string arg tidak dapat memiliki beberapa simbol lain seperti ','meskipun
pterodragon

Perlu di-tweak untuk parens, karena int count = NUMARGS( foo(1, 2) );menghasilkan 2 daripada 1. godbolt.org/z/kpBuOm
jorgbrown

Ini tidak akan berfungsi seperti yang diharapkan dengan lambda, pemanggilan fungsi atau apa pun yang mungkin berisi koma tambahan dalam parameter.
Nandee

2

di sini cara sederhana untuk menghitung 0 atau lebih argumen dari VA_ARGS , contoh saya mengasumsikan maksimal 5 variabel, tetapi Anda dapat menambahkan lebih banyak jika Anda mau.

#define VA_ARGS_NUM_PRIV(P1, P2, P3, P4, P5, P6, Pn, ...) Pn
#define VA_ARGS_NUM(...) VA_ARGS_NUM_PRIV(-1, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0)


VA_ARGS_NUM()      ==> 0
VA_ARGS_NUM(19)    ==> 1
VA_ARGS_NUM(9, 10) ==> 2
         ...

Sayangnya pendekatan ini tidak berfungsi dengan benar saat VA_ARGS_NUMdigunakan dengan makro: jika saya memiliki #define TEST(yaitu kosong TEST) dan VA_ARGS_NUM(TEST)tidak mengembalikan 0 (nol) saat digunakan dalam #if:(
AntonK

@AntonK dapatkah Anda memposting apa yang telah Anda lakukan tepatnya?
elhadi dp ıpɐɥ ן ǝ

0

Anda dapat merangkai dan menghitung token:

int countArgs(char *args)
{
  int result = 0;
  int i = 0;

  while(isspace(args[i])) ++i;
  if(args[i]) ++result;

  while(args[i]) {
    if(args[i]==',') ++result;
    else if(args[i]=='\'') i+=2;
    else if(args[i]=='\"') {
      while(args[i]) {
        if(args[i+1]=='\"' && args[i]!='\\') {
          ++i;
          break;
        }
        ++i;
      }
    }
    ++i;
  }

  return result;
}

#define MACRO(...) \
{ \
  int count = countArgs(#__VA_ARGS__); \
  printf("NUM ARGS: %d\n",count); \
}

2
Baru saja melihat hasil edit yang menunggu keputusan pada jawaban ini - tampaknya Anda mungkin memiliki dua akun. Jika Anda tetap menggunakannya, Anda akan dapat mengedit posting Anda sendiri tanpa harus mendapat persetujuan.
J Richard Snape

0

Boost Preprocessor sebenarnya memiliki ini pada Boost 1.49, sebagai BOOST_PP_VARIADIC_SIZE(...). Ini berfungsi hingga ukuran 64.

Di bawah tenda, pada dasarnya sama dengan jawaban Kornel Kisielewicz .


@Carlcah Praprosesor sebenarnya tidak memiliki konsep "argumen nol". Apa yang kita anggap sebagai "argumen nol" adalah "satu argumen kosong" di preprocessor. Tapi itu bisa diperbaiki menggunakan C ++ 20 __VA_OPT__atau ekstensi kompilator untuk ##__VA_ARGS__menghapus koma sebelumnya, misalnya: godbolt.org/z/X7OvnK
Justin

0

Saya telah menemukan jawaban di sini masih belum lengkap.

Implementasi portabel terdekat yang saya temukan dari sini adalah: C ++ preprocessor __VA_ARGS__ jumlah argumen

Tapi itu tidak bekerja dengan argumen nol di GCC tanpa setidaknya -std=gnu++11parameter baris perintah.

Jadi saya memutuskan untuk menggabungkan solusi ini dengan itu: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

#define UTILITY_PP_CONCAT_(v1, v2) v1 ## v2
#define UTILITY_PP_CONCAT(v1, v2) UTILITY_PP_CONCAT_(v1, v2)

#define UTILITY_PP_CONCAT5_(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4

#define UTILITY_PP_IDENTITY_(x) x
#define UTILITY_PP_IDENTITY(x) UTILITY_PP_IDENTITY_(x)

#define UTILITY_PP_VA_ARGS_(...) __VA_ARGS__
#define UTILITY_PP_VA_ARGS(...) UTILITY_PP_VA_ARGS_(__VA_ARGS__)

#define UTILITY_PP_IDENTITY_VA_ARGS_(x, ...) x, __VA_ARGS__
#define UTILITY_PP_IDENTITY_VA_ARGS(x, ...) UTILITY_PP_IDENTITY_VA_ARGS_(x, __VA_ARGS__)

#define UTILITY_PP_IIF_0(x, ...) __VA_ARGS__
#define UTILITY_PP_IIF_1(x, ...) x
#define UTILITY_PP_IIF(c) UTILITY_PP_CONCAT_(UTILITY_PP_IIF_, c)

#define UTILITY_PP_HAS_COMMA(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))
#define UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_(...) ,

#define UTILITY_PP_IS_EMPTY(...) UTILITY_PP_IS_EMPTY_( \
    /* test if there is just one argument, eventually an empty one */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__),                                \
    /* test if _TRIGGER_PARENTHESIS_ together with the argument adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__), \
    /* test if the argument together with a parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(__VA_ARGS__ ()),                             \
    /* test if placing it between _TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */ \
    UTILITY_PP_HAS_COMMA(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()))

#define UTILITY_PP_IS_EMPTY_(_0, _1, _2, _3) UTILITY_PP_HAS_COMMA(UTILITY_PP_CONCAT5_(UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define UTILITY_PP_IS_EMPTY_IS_EMPTY_CASE_0001 ,

#define UTILITY_PP_VA_ARGS_SIZE(...) UTILITY_PP_IIF(UTILITY_PP_IS_EMPTY(__VA_ARGS__))(0, UTILITY_PP_VA_ARGS_SIZE_(__VA_ARGS__, UTILITY_PP_VA_ARGS_SEQ64()))
#define UTILITY_PP_VA_ARGS_SIZE_(...) UTILITY_PP_IDENTITY(UTILITY_PP_VA_ARGS_TAIL(__VA_ARGS__))

#define UTILITY_PP_VA_ARGS_TAIL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14, x, ...) x
#define UTILITY_PP_VA_ARGS_SEQ64() 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0

#define EATER0(...)
#define EATER1(...) ,
#define EATER2(...) (/*empty*/)
#define EATER3(...) (/*empty*/),
#define EATER4(...) EATER1
#define EATER5(...) EATER2
#define MAC0() ()
#define MAC1(x) ()
#define MACV(...) ()
#define MAC2(x,y) whatever

static_assert(UTILITY_PP_VA_ARGS_SIZE() == 0, "1");
static_assert(UTILITY_PP_VA_ARGS_SIZE(/*comment*/) == 0, "2");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a) == 1, "3");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b) == 2, "4");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c) == 3, "5");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d) == 4, "6");
static_assert(UTILITY_PP_VA_ARGS_SIZE(a, b, c, d, e) == 5, "7");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void)) == 1, "8");
static_assert(UTILITY_PP_VA_ARGS_SIZE((void), b, c, d) == 4, "9");
static_assert(UTILITY_PP_VA_ARGS_SIZE(UTILITY_PP_IS_EMPTY_TRIGGER_PARENTHESIS_) == 1, "10");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER0) == 1, "11");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER1) == 1, "12");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER2) == 1, "13");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER3) == 1, "14");
static_assert(UTILITY_PP_VA_ARGS_SIZE(EATER4) == 1, "15");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC0) == 1, "16");
// a warning in msvc
static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC1) == 1, "17");
static_assert(UTILITY_PP_VA_ARGS_SIZE(MACV) == 1, "18");
// This one will fail because MAC2 is not called correctly
//static_assert(UTILITY_PP_VA_ARGS_SIZE(MAC2) == 1, "19");

https://godbolt.org/z/3idaKd

  • c++11, msvc 2015, gcc 4.7.1,clang 3.0
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.