ตัวประมวลผลล่วงหน้า C ++ __VA_ARGS__ จำนวนอาร์กิวเมนต์


100

คำถามง่ายๆที่หาคำตอบในเน็ตไม่ได้ ในมาโครอาร์กิวเมนต์ตัวแปรจะหาจำนวนอาร์กิวเมนต์ได้อย่างไร ฉันโอเคกับบูสต์พรีโปรเซสเซอร์หากมีวิธีแก้ปัญหา

ถ้ามันสร้างความแตกต่างฉันกำลังพยายามแปลงจำนวนอาร์กิวเมนต์มาโครที่แปรผันเพื่อเพิ่มลำดับตัวประมวลผลล่วงหน้ารายการหรืออาร์เรย์สำหรับการประมวลผลใหม่เพิ่มเติม


เพื่อความชัดเจน - คุณกำลังถามเกี่ยวกับมาโครตัวแปรไม่ใช่มาโครที่ใช้สร้างฟังก์ชัน C แบบแปรผันใช่หรือไม่

2
อาร์กิวเมนต์ประเภทเดียวกันหรือไม่ ถ้าเป็นเช่นนั้นและหากทราบประเภทจะมีโซลูชัน C มาตรฐานผ่านตัวอักษรผสม หากไม่ทราบคุณสามารถใช้__typeof__เพื่อให้มันทำงานได้อย่างน้อยในคอมไพเลอร์บางตัว
Christoph

1
เนื่องจากการสนทนาเกี่ยวกับลำดับตัวประมวลผลก่อน Boost เป็นต้นจึงต้องเป็น C ++ (ซึ่งเป็นสาเหตุที่ฉันติดแท็ก Q ใหม่ แต่ไม่สามารถเปลี่ยนชื่อคำถามได้) ... อ๊ะ; ฉันจะแก้ไข
Jonathan Leffler

@JonathanLeffler True, Boost เป็นไลบรารี C ++ อย่างไรก็ตาม Boost.Preprocessor สามารถใช้ได้กับ C. AFAIK ไม่มีอะไรที่ใช้เฉพาะ C ++
จัสติน

ที่เกี่ยวข้อง: stackoverflow.com/questions/11761703/…
Gabriel Staples

คำตอบ:


91

สิ่งนี้ขึ้นอยู่กับคอมไพเลอร์จริงและไม่ได้รับการสนับสนุนจากมาตรฐานใด ๆ

อย่างไรก็ตามคุณมีการใช้งานมาโครที่จะนับ:

#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

.... แต่ตอนนี้เป็นมาตรฐานใน C ++ 0x และน่าจะนานกว่านี้แล้วเพราะเป็นวิธีที่ยอดเยี่ยมในการป้องกันฟังก์ชัน varadic จากการโทรที่เสียหาย (กล่าวคือคุณสามารถส่งผ่านค่าหลังจากรายการ varadic นี่เป็นวิธีที่จริง ของการนับที่ฉันเคยใช้ แต่ฉันคิดว่าขนาดของมันก็ใช้ได้เช่นกัน ..
osirisgothra

คำตอบเชื่อมโยงไปยังไซต์อื่น ดูเหมือนว่าลิงก์จะไม่ชี้ไปที่คำตอบที่ถูกต้อง และแม้ว่าฉันจะหาคำตอบที่ตั้งใจไว้ได้ แต่มันก็ดูน่าสงสารเพราะมันฝังฮาร์ดโค้ด "-1" ที่จะคอมไพล์ มีวิธีการที่ดีกว่านี้
ceztko

2
ขอบคุณ! สิ่งนี้ใช้ได้ผลใน Visual Studio 2013 สำหรับฉัน: #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()ไม่สามารถคืนค่า 0 ได้GET_ARG_COUNT()& Y_TUPLE_SIZE()โซลูชันทำงาน
PSkocik

1
" PP_NARG()ไม่คืนค่า 0" ... ไม่จำเป็นต้องเป็นปัญหา เราสามารถพูดได้ว่าPP_NARG() ควรส่งคืน 1 ด้วยเหตุผลเดียวกันPP_NARG(,)ควรส่งคืน 2 การตรวจจับ 0 อาจมีประโยชน์ในบางกรณี แต่การแก้ปัญหาดูเหมือนจะไม่ค่อยกว้างนัก (ต้องการให้โทเค็นแรกนั้นสามารถวางได้ซึ่งอาจจะหรือไม่ก็ได้ ขึ้นอยู่กับว่าคุณใช้มันเพื่ออะไร) หรือการใช้งานที่เฉพาะเจาะจง (เช่นต้องการเคล็ดลับการลบ - วางคอมมาของ gnu)
H Walters

101

ฉันมักจะใช้มาโครนี้เพื่อค้นหาพารามิเตอร์ต่างๆ:

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

ตัวอย่างเต็ม:

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

เป็นรหัส C99 ที่ถูกต้องอย่างสมบูรณ์ แต่ก็มีหนึ่งข้อเสียเปรียบแม้ว่า - คุณไม่สามารถเรียกใช้แมโครSUM()โดยไม่ต้อง params แต่ GCC มีวิธีการแก้ปัญหากับมัน - ดูที่นี่

ดังนั้นในกรณีของ GCC คุณต้องกำหนดมาโครดังนี้:

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

และจะทำงานได้แม้จะมีรายการพารามิเตอร์ว่าง


4
อืมมันใช้ไม่ได้กับ OP เขาต้องการขนาดสำหรับ BOOST_PP ซึ่งทำงานตามเวลาคอมไพล์
Kornel Kisielewicz

5
ฉลาด! มันทำงานเมื่อไหร่sizeof(int) != sizeof(void *)?
Adam Liss

3
@Kornel เช่นเดียวกับมาโครใด ๆ จะได้รับการประเมินในเวลาคอมไพล์ ฉันไม่รู้เกี่ยวกับ Boost แต่ไม่จำเป็นต้องใช้ Boost
qrdl

4
@Adam เพราะฉันส่ง{__VA_ARGS__}ไปint[]มันเป็นเพียงint[]โดยไม่คำนึงถึงเนื้อหาจริงของ__VA_ARGS__
qrdl

3
วิธีแก้สวยหรู! ทำงานใน VS2017 ##ไม่จำเป็นต้องใช้ใน VS2017 เป็นที่ว่างเปล่า__VA_ARGS__จะลบเครื่องหมายจุลภาคก่อนหน้านี้โดยอัตโนมัติ
poby

42

หากคุณใช้ C ++ 11 และคุณต้องการค่าเป็นค่าคงที่เวลาคอมไพล์ C ++ วิธีแก้ปัญหาที่สวยงามมากคือ:

#include <tuple>

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

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


2
ทางออกที่ดี! และไม่เหมือนที่sizeof((int[]){__VA_ARGS__})/sizeof(int)แนะนำไว้ข้างต้นมันใช้งานได้แม้ว่าอาร์กิวเมนต์จะไม่สามารถส่งไปintได้ทั้งหมด
วิม

ตกลง ทางออกที่ดี! ++.
davernator

ใช้ไม่ได้กับเทมเพลตเช่น NUMARGS (sum <1,2>); ดูgodbolt.org/z/_AAxmL
jorgbrown

1
ฉัน ... คิดว่านั่นอาจเป็นประเด็นที่น่ายินดีสำหรับเรื่องนี้ @jorgbrown อย่างน้อยก็ในกรณีส่วนใหญ่ที่มันเกิดขึ้น เนื่องจากมันต้องอาศัยคอมไพเลอร์แทนตัวประมวลผลก่อนเพื่อทำการนับจึงให้จำนวนอาร์กิวเมนต์ตามที่คอมไพเลอร์เห็นซึ่งน่าจะตรงกับสิ่งที่โปรแกรมเมอร์ส่วนใหญ่คาดหวัง มันจะทำให้เกิดปัญหาหากคุณคาดว่าจะต้องคำนึงถึงความโลภของตัวประมวลผลล่วงหน้า
Justin Time - คืนสถานะ Monica

คำตอบที่ยอดเยี่ยม คุณสามารถใส่ลงในมาโครได้#define NUM_ARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
Richard Whitehead

23

เพื่อความสะดวกของที่นี่มีการใช้งานที่ทำงานสำหรับ 0-70 ข้อโต้แย้งและผลงานใน Visual Studio, GCC และเสียงดังกราว ฉันเชื่อว่ามันจะใช้งานได้ใน Visual Studio 2010 และใหม่กว่า แต่ได้ทดสอบใน 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 ตัวแปรของ Microsoft ล้มเหลวเนื่องจากไม่มีอาร์กิวเมนต์
Vroomfondel

@Vroomfondel ตัวแปรของ Microsoft ทำงานสำหรับอาร์กิวเมนต์เป็นศูนย์ static_assert ตัวแรกในตัวอย่างด้านบนเป็นการทดสอบเฉพาะสำหรับกรณีที่ไม่มีอาร์กิวเมนต์และฉันเพิ่งรวบรวมและรันบน Visual Studio 2017 v15.8.9
Chris Kline

น่าสนใจ - การใช้ตัวแปรของ Microsoft บนคอมไพเลอร์ที่ไม่ใช่ของ Microsoft ไม่ได้ผล - คุณรู้หรือไม่ว่าตัวประมวลผลล่วงหน้า M $ ทำอะไรแตกต่างกันซึ่งทำให้โค้ดทำงานในทางตรงกันข้าม BTW ฉันลอง C ไม่ใช่ C ++
Vroomfondel

ฉันเชื่อว่าเป็นเพราะ MSVC ดีกว่าเล็กน้อยเกี่ยวกับ "zero-length __VA_ARGS__" (ซึ่งใน C ++ เป็นส่วนขยายของคอมไพเลอร์(ใกล้เคียงสากลมาตรฐานโดยพฤตินัย ) จนถึง C ++ 20) ส่วนใหญ่ (? ทั้งหมด) คอมไพเลอร์ช่วยให้ความยาวเป็นศูนย์ แต่สำลักจุลภาคต่อท้ายถ้ารายการเป็นที่ว่างเปล่า (และเกินพิกัด##เป็น proto- __VA_OPT__เพื่อลบเครื่องหมายจุลภาคในกรณีนี้); ส่วนขยายเวอร์ชันของ MSVC ไม่สำลักลูกน้ำ (แต่จะทำให้หายใจไม่ออกเมื่อโหลดมากเกินไป##) เปรียบเทียบ MSVC unused, __VA_ARGS__กับไม่ใช่ MSVC 0, ## __VA_ARGS__; ไม่ถูกต้องมากกว่าปัญหาก็คือว่ามันต่างกัน
Justin Time - คืนสถานะ Monica

ฉันไม่แน่ใจว่าสิ่งนี้เหมือนกันใน C หรือไม่ แต่ @Vroomfondel เนื่องจากฉันทำบุ๊กมาร์กหายไปในแบบร่างล่าสุด
Justin Time - คืนสถานะ Monica

11

มีโซลูชัน C ++ 11 บางอย่างสำหรับการค้นหาจำนวนอาร์กิวเมนต์ในเวลาคอมไพล์ แต่ฉันประหลาดใจที่เห็นว่าไม่มีใครแนะนำอะไรง่ายๆเช่น:

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

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

สิ่งนี้ไม่จำเป็นต้องรวม<tuple>ส่วนหัวไว้ด้วย


1
"แต่ทำไมไม่ใช้เทมเพลตและ sizeof ... แทน (เหมือนในคำตอบของฉันเอง)" c ++ กลายเป็นสัตว์ประหลาด มันมีคุณสมบัติมากเกินไปและมีหลายอย่างเช่นเทมเพลตแบบต่างๆที่ไม่ค่อยมีใครใช้ คุณอ่านเกี่ยวกับเรื่องนี้คุณเขียนตัวอย่างแล้วคุณก็ลืมไป ดังนั้นจึงเป็นเรื่องยากที่จะเกิดความคิดที่ถูกต้องในเวลาที่เหมาะสม เนื่องจากโซลูชันของคุณดูเหมือนจะเป็นทางเลือกที่ดีกว่าของฉันฉันจะปล่อยให้การคัดเลือกโดยธรรมชาติทำงานและฉันจะลบโซลูชันของฉัน
zdf

1
@ZDF เข้าใจได้ แต่ฉันใช้เทมเพลตตัวแปรอยู่ตลอดเวลา โปรแกรมของฉันมีประสิทธิภาพมากขึ้นตั้งแต่ C ++ 11 และนี่คือหนึ่งในเหตุผลหลักว่าทำไม ฉันคิดว่าไม่จำเป็นต้องลบคำตอบของคุณ
monkey0506

1
VA_COUNT(&,^,%)มันจะไม่ทำงานกับบเช่น นอกจากนี้หากคุณกำลังนับผ่านทาง funtion ฉันไม่เห็นเหตุผลใด ๆ ในการสร้างมาโคร
Qwertiy

การแก้ปัญหานี้ยังคงเป็นคำถาม: พารามิเตอร์ของ VA_COUNT เป็นตัวระบุทั้งหมดที่ยังไม่ได้กำหนดเป็นตัวแปรหรือบางอย่างและทำให้เกิดข้อผิดพลาด '*** ตัวแปรไม่ได้กำหนด' มีวิธีใดในการแก้ไขปัญหานี้หรือไม่?
ipid

8

สิ่งนี้ใช้ได้กับ 0 อาร์กิวเมนต์ด้วย gcc / llvm [ลิงก์ไม่น่าสนใจ]

/*
 * 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 ดูเหมือนจะไม่สนใจตัวดำเนินการ ## ที่ใช้ในการใช้อาร์กิวเมนต์ว่าง คุณสามารถหลีกเลี่ยงสิ่งนั้นได้ด้วยสิ่งที่ชอบ

#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

ฉันทดสอบสิ่งนี้สำหรับ Visual Studio 2008 และใช้ไม่ได้กับ 0 อาร์กิวเมนต์ COUNT_ARGS () = 1
user720594

ดูเหมือนว่าลิงก์จะเสีย
ม.ค. Smrčina

ลิงก์ถาวร VS ต้องทำอะไรที่แตกต่างเป็นธรรมดา :). ฉันไม่คิดว่าพวกเขาจะสนับสนุน C99 อย่างเต็มที่ในเร็ว ๆ นี้
user1187902

2
เอ่อ##__VA_ARGS__กินลูกน้ำก่อนถ้า__VA_ARGS__ว่างคือนามสกุล GCC ไม่ใช่พฤติกรรมมาตรฐาน
คดีของ Fund Monica

6

ด้วยนามสกุล msvc:

#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

ใช้ได้กับ 0-32 อาร์กิวเมนต์ ขีด จำกัด นี้สามารถขยายได้ง่าย

แก้ไข: เวอร์ชันที่เรียบง่าย (ทำงานใน VS2015 14.0.25431.01 อัปเดต 3 & gcc 7.4.0) ได้ถึง 100 อาร์กิวเมนต์เพื่อคัดลอกและวาง:

#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
เป็นแค่ฉันเองหรือนี่มันแหกกฎกลิ่นรหัส .. ?
osirisgothra

มันใช้ได้กับฉันกับ VC ++ จนถึง VS2012 เป็นอย่างน้อยและ GCC และเสียงดังในการทดสอบพื้นฐานของฉัน
ThreeBit

@osirisgothra ทำไมมันถึงมีกลิ่น?
ceztko

แม้ว่ามาโครนี้จะรองรับคอมไพเลอร์แบบกว้าง ๆ แต่ก็ไม่สามารถใช้กับอาร์กิวเมนต์มาโครเช่นสตริงได้เช่นY_TUPLE_SIZE("Hello")ทำให้ไม่สามารถทำได้ ฉันเห็นด้วยกับ @osirisgothra
ceztko

1
มาโครนี้สามารถทำงานให้คุณได้ แต่มีข้อบกพร่องร้ายแรง ฉันทำการวิจัยมากมายและพบแนวทางที่สะอาดกว่าซึ่งใช้ได้ผลใน GCC และ VS คุณสามารถค้นหาได้ในคำตอบของฉันสำหรับคำถามที่คล้ายกัน
ceztko

3

ฉันสมมติว่าแต่ละอาร์กิวเมนต์ของVA_ARGSจะถูกคั่นด้วยเครื่องหมายจุลภาค ถ้าเป็นเช่นนั้นฉันคิดว่านี่น่าจะเป็นวิธีที่ค่อนข้างสะอาดในการทำเช่นนี้

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

ทำงานให้ฉันกับ godbolt สำหรับ clang 4 และ GCC 5.1 สิ่งนี้จะคำนวณในเวลาคอมไพล์ แต่จะไม่ประเมินสำหรับตัวประมวลผลล่วงหน้า ดังนั้นหากคุณพยายามทำบางอย่างเช่นสร้างFOR_EACHสิ่งนี้จะไม่ได้ผล


คำตอบนี้มีการประเมินต่ำ มันจะใช้ได้แม้กระทั่งNUMARGS(hello, world = 2, ohmy42, !@#$%^&*()-+=)!!! สตริงหาเรื่องแต่ละคนไม่สามารถมีบางสัญลักษณ์อื่น ๆ เช่น','แม้ว่า
pterodragon

ต้องได้รับการปรับแต่งสำหรับ parens เพราะint count = NUMARGS( foo(1, 2) );สร้าง 2 มากกว่า 1. godbolt.org/z/kpBuOm
jorgbrown

สิ่งนี้จะไม่ทำงานตามที่คาดไว้กับ lambdas การเรียกฟังก์ชันหรือสิ่งอื่นใดที่อาจมีเครื่องหมายจุลภาคเพิ่มเติมในพารามิเตอร์
Nandee

ดูเหมือนจะใช้ไม่ได้กับ 0 args ใน MSVC ..
n00bmind

2

ต่อไปนี้เป็นวิธีง่ายๆในการนับอาร์กิวเมนต์VA_ARGS 0 หรือมากกว่าตัวอย่างของฉันสมมติว่ามีตัวแปรสูงสุด 5 ตัว แต่คุณสามารถเพิ่มได้มากขึ้นหากต้องการ

#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
         ...

น่าเสียดายที่วิธีการทำงานไม่ถูกต้องเมื่อVA_ARGS_NUMใช้กับมาโคร: ถ้าฉันมี#define TEST(เช่นว่างเปล่าTEST) และVA_ARGS_NUM(TEST)ไม่ส่งคืน 0 (ศูนย์) เมื่อใช้ใน#if:(
AntonK

@AntonK โพสต์สิ่งที่คุณทำลงไปได้ไหม?
elhadi dp ıpɐɥןǝ

0

คุณสามารถ stringfy และนับโทเค็น:

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
เพิ่งดูการแก้ไขที่รอดำเนินการกับคำตอบนี้ดูเหมือนว่าคุณอาจมีสองบัญชี หากคุณยึดติดกับข้อใดข้อหนึ่งคุณจะสามารถแก้ไขโพสต์ของคุณเองได้โดยไม่ต้องขออนุมัติ
J Richard Snape

0

Boost Preprocessor มีสิ่งนี้อยู่ที่ Boost 1.49 เช่นเดียวกับBOOST_PP_VARIADIC_SIZE(...). ทำงานได้ถึงขนาด 64

ภายใต้ฝากระโปรงก็เป็นเช่นเดียวกับคำตอบของคอร์เนลคิซีลวิซ ซ์


@CarloWood แน่นอน. ตัวประมวลผลก่อนไม่มีแนวคิดของ "อาร์กิวเมนต์เป็นศูนย์" จริงๆ สิ่งที่เราคิดว่าเป็น "อาร์กิวเมนต์เป็นศูนย์" คือ "อาร์กิวเมนต์ว่างหนึ่งรายการ" ในตัวประมวลผลล่วงหน้า แต่สามารถแก้ไขได้โดยใช้ C ++ 20 __VA_OPT__หรือส่วนขยายของคอมไพเลอร์เพื่อ##__VA_ARGS__ลบเครื่องหมายจุลภาคก่อนหน้าเช่นgodbolt.org/z/X7OvnK
Justin

0

ฉันพบคำตอบที่นี่ยังไม่สมบูรณ์

การใช้งานแบบพกพาที่ใกล้เคียงที่สุดที่ฉันพบจากที่นี่คือ: C ++ พรีโปรเซสเซอร์ __VA_ARGS__ จำนวนอาร์กิวเมนต์

แต่ใช้ไม่ได้กับอาร์กิวเมนต์เป็นศูนย์ใน GCC โดยไม่มี-std=gnu++11พารามิเตอร์บรรทัดคำสั่งเป็นอย่างน้อย

ดังนั้นฉันจึงตัดสินใจที่จะรวมโซลูชันนี้เข้ากับสิ่งนั้น: 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.