GCC'nin ## __ VA_ARGS__ numarasına standart bir alternatif mi?


154

C99'daki değişken makrolar için boş bağımsız değişkenlerle ilgili iyi bilinen bir sorun var.

misal:

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

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

Yukarıdakilerin kullanımı BAR(), C99 standardına göre gerçekten yanlıştır, çünkü aşağıdakilere genişleyecektir:

printf("this breaks!",);

Sondaki virgülü not edin - çalışmaz.

Bazı derleyiciler (örneğin: Visual Studio 2010) sizin için sondaki virgülden sessizce kurtulacaktır. Diğer derleyiciler (örneğin: GCC) aşağıdakilerin ##önüne koymayı destekler __VA_ARGS__:

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

Ancak bu davranışı elde etmenin standartlara uygun bir yolu var mı? Belki birden çok makro kullanıyor musunuz?

Şu anda, ##sürüm oldukça iyi destekleniyor gibi görünüyor (en azından benim platformlarımda), ancak gerçekten standartlara uygun bir çözüm kullanmayı tercih ederim.

Önleyici: Küçük bir fonksiyon yazabileceğimi biliyorum. Bunu makro kullanarak yapmaya çalışıyorum.

Düzenleme : BAR () neden kullanmak istediğime dair bir örnek (basit olsa da):

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

Bu fmt, her zaman çift tırnaklı bir C-dizesi olduğunu varsayarak , BAR () günlük ifadelerime otomatik olarak bir satırsonu ekler . Yeni satırı ayrı bir printf () olarak YAZDIRMAZ; bu, günlük kaydı satır arabelleğe alınmışsa ve birden çok kaynaktan eşzamansız olarak geliyorsa avantajlıdır.


3
Neden ilk etapta BARyerine kullanasınız FOO?
GManNickG

@GMan: Sonuna bir örnek ekledim
jwd

5
@GMan: Son cümleyi oku (:
jwd

7
Bu özellik C2x'e dahil edilmek üzere önerilmiştir.
Leushenko

2
@zwol, WG14'e gönderilen en son sürüm , anahtar kelimeye dayalı yeni bir sözdizimi kullanan buna benzer__VA_OPT__ . Bu zaten C ++ tarafından "benimsenmiştir" , bu yüzden C'nin de aynı şeyi yapmasını bekliyorum. (bunun C ++ 17'ye hızlı izlendiğini mi yoksa C ++ 20 için mi ayarlandığını
bilmiyorum

Yanıtlar:


68

Richard Hansen'ın bu soruya cevabında,##__VA_ARGS__ açıklandığı gibi, değişken makronuza iletebileceğiniz argümanların sayısının sabit kodlu bir üst sınırını kabul etmeye istekliyseniz , GCC'nin uzantısının kullanımından kaçınmanız mümkündür . Bununla birlikte, böyle bir sınıra sahip olmak istemiyorsanız, bildiğim kadarıyla, yalnızca C99'da belirtilen ön işlemci özelliklerini kullanmak mümkün değildir; dilin bazı uzantılarını kullanmalısınız. clang ve icc bu GCC uzantısını benimsedi, ancak MSVC almadı.

2001 yılında, standardizasyon için GCC uzantısını (ve __VA_ARGS__rest parametresi dışında bir ad kullanmanıza izin veren ilgili uzantıyı ) N976 belgesine yazdım , ancak bu, komiteden herhangi bir yanıt almadı; Kimsenin okuyup okumadığını bile bilmiyorum. 2016 yılında yeniden önerildi N2023'te ve bu teklifin yorumlarda bize nasıl haber vereceğini bilen herkesi teşvik ediyorum.


2
Web'de bir çözüm bulma konusundaki
engelime

2
n976 kastediyorsun ne olacak? Ben geri kalanını arandı C çalışma grubu 'ın belgeler bir yanıt ama asla bulunan. Sonraki toplantının gündeminde bile yoktu . Bu konudaki diğer tek sonuç , C99'un onaylanmasından önce Norveç'in n868'deki 4 numaralı yorumuydu (yine takip tartışması olmadan).
Richard Hansen

4
Evet, özellikle bunun ikinci yarısı. Hakkında tartışma olmuş olabilir, comp.std.cancak şu anda Google Grupları'nda hiç bulamadım; Kesinlikle gerçek komiteden hiç ilgi görmedi (ya da olduysa, kimse bana bundan bahsetmedi).
zwol

1
Korkarım elimde bir kanıt yok ve artık bir tane düşünmeye çalışacak doğru kişi de değilim. GCC'nin ön işlemcisinin yarısını yazdım, ancak bu on yıldan daha önceydi ve o zaman bile aşağıdaki argüman sayma hilesini hiç düşünmemiştim.
zwol

6
Bu eklenti clang ve intel icc derleyicilerinin yanı sıra gcc ile de çalışır.
ASİKLİK

114

Kullanabileceğiniz bir argüman sayma numarası var.

İşte BAR()jwd'nin sorusuna ikinci örneği uygulamanın standartlara uygun bir yolu :

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

Aynı numara şu amaçlarla kullanılır:

Açıklama

Strateji, __VA_ARGS__birinci argümana ve varsa geri kalanına ayırmaktır . Bu, ilk bağımsız değişkenden sonra ancak ikinciden önce (varsa) bir şeyler eklemeyi mümkün kılar.

FIRST()

Bu makro basitçe ilk argümana genişler ve gerisini atar.

Uygulama basittir. throwawayArgüman olmasını sağlar FIRST_HELPER()çünkü gereklidir iki argüman alır, ...az birinde ihtiyaçlarını. Bir argümanla aşağıdaki gibi genişler:

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

İki veya daha fazlasıyla şu şekilde genişler:

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

REST()

Bu makro, ilk bağımsız değişken dışındaki her şeye genişler (birden fazla bağımsız değişken varsa, ilk bağımsız değişkenden sonraki virgül dahil).

Bu makronun uygulanması çok daha karmaşıktır. Genel strateji, bağımsız değişkenlerin sayısını (bir veya daha fazla) saymak ve ardından REST_HELPER_ONE()(yalnızca bir bağımsız değişken verilmişse) veya REST_HELPER_TWOORMORE()(iki veya daha fazla bağımsız değişken verilmişse) şeklinde genişletmektir . REST_HELPER_ONE()basitçe hiçbir şeye genişler - ilkinden sonra hiçbir argüman yoktur, bu nedenle kalan argümanlar boş kümedir. REST_HELPER_TWOORMORE()aynı zamanda basittir - virgülle genişler ve ardından ilk argüman hariç her şey gelir.

Bağımsız değişkenler, NUM()makro kullanılarak sayılır . Bu makro, ONEyalnızca bir argüman verilirse genişler , TWOORMOREeğer iki ila dokuz argüman verilirse ve 10 veya daha fazla argüman verilirse kırılır (çünkü 10. argümana genişler).

NUM()Makro kullanan SELECT_10TH()argümanlar sayısını belirlemek için makro. Adından da anlaşılacağı gibi, SELECT_10TH()basitçe 10. argümanına genişler. Üç nokta nedeniyle, SELECT_10TH()en az 11 argüman iletilmesi gerekir (standart, üç nokta için en az bir argüman olması gerektiğini söyler). Bu nedenle , son argüman olarak NUM()geçer throwaway(onsuz, bir argümanı iletmek NUM(), yalnızca 10 argümanın iletilmesine neden olur.SELECT_10TH() , standardı ihlal edecek neden olur).

Ya seçimi REST_HELPER_ONE()veya REST_HELPER_TWOORMORE()birleştirerek yapılır REST_HELPER_genişlemesi ile NUM(__VA_ARGS__)de REST_HELPER2(). Amacının, bir araya getirilmeden önce tamamen genişletilmesini REST_HELPER()sağlamak olduğunu unutmayın .NUM(__VA_ARGS__)REST_HELPER_

Bir argümanla genişletme aşağıdaki gibidir:

  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. (boş)

İki veya daha fazla bağımsız değişkenle genişletme aşağıdaki gibidir:

  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 veya daha fazla argümanlarla BAR ararsanız Not bu başarısız olacağını ve daha argümanları uzatmak nispeten kolay olsa da, her zaman bir üst o başa çıkabilirim argümanlar sayısına bağlı olacak
Chris Dodd

2
@ChrisDodd: Doğru. Ne yazık ki, derleyiciye özgü uzantılara güvenmeden bağımsız değişkenlerin sayısında bir sınırdan kaçınmanın bir yolu yok gibi görünüyor. Ayrıca, çok fazla argüman olup olmadığını güvenilir bir şekilde test etmenin bir yolunun da farkında değilim (böylece garip bir hata yerine yararlı bir derleyici hata mesajı yazdırılabilir).
Richard Hansen

18

Genel bir çözüm değil, ancak printf durumunda şöyle bir yeni satır ekleyebilirsiniz:

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

Biçim dizesinde referans verilmeyen fazladan argümanları yok saydığına inanıyorum. Yani muhtemelen şunlardan kurtulabilirsiniz:

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

Bunu yapmanın standart bir yolu olmadan C99'un onaylandığına inanamıyorum. AFAICT sorun C ++ 11'de de var.


Bu fazladan 0 ile ilgili sorun, vararg işlevini çağırırsa, aslında koda düşecek olmasıdır. Richard Hansen tarafından sağlanan çözümü kontrol edin
Pavel P

1
@Pavel ikinci örnek konusunda haklı ama ilki harika çalışıyor. +1.
kirbyfan64sos

11

Bu özel durumu Boost.Preprocessor gibi bir şey kullanarak halletmenin bir yolu var . Bağımsız değişken listesinin boyutunu kontrol etmek için BOOST_PP_VARIADIC_SIZE'ı kullanabilir ve ardından koşullu olarak başka bir makroya genişletebilirsiniz. Bunun bir dezavantajı, 0 ile 1 argümanı arasında ayrım yapamamasıdır ve bunun nedeni aşağıdakileri göz önünde bulundurduğunuzda netleşir:

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

Boş makro argüman listesi aslında boş olan bir argümandan oluşur.

Bu durumda, şanslıyız çünkü istediğiniz makronuz her zaman en az 1 argümana sahip olduğundan, bunu iki "aşırı yük" makrosu olarak uygulayabiliriz:

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

Ve sonra aralarında geçiş yapmak için başka bir makro, örneğin:

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

veya

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

Hangisini daha okunaklı bulursanız bulun (size, makroların bağımsız değişkenlerin sayısı üzerine aşırı yüklenmesi için genel bir form verdiği için ilkini tercih ederim).

Değişken argümanlar listesine erişip bunları değiştirerek bunu tek bir makroyla yapmak da mümkündür, ancak bu çok daha az okunabilir ve bu soruna çok özeldir:

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

Ayrıca, neden BOOST_PP_ARRAY_ENUM_TRAILING yok? Bu çözümü çok daha az korkunç hale getirir.

Düzenleme: Pekala, işte bir BOOST_PP_ARRAY_ENUM_TRAILING ve onu kullanan bir sürüm (bu artık benim en sevdiğim çözüm):

#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 hakkında bilgi edinmek güzel. Not BOOST_PP_VARIADIC_SIZE()cevabım belgelenen aynı argüman sayma hüner I kullanır ve aynı sınırlamayı (eğer bağımsız değişkenler belirli sayıdan fazla geçirirseniz o kıracak) sahiptir.
Richard Hansen

1
Evet, yaklaşımınızın Boost tarafından kullanılanla aynı olduğunu gördüm, ancak artırma çözümü çok iyi korunuyor ve daha karmaşık makrolar geliştirirken kullanmak için başka birçok gerçekten yararlı özelliğe sahip. Özyineleme maddeleri özellikle harikadır (ve BOOST_PP_ARRAY_ENUM kullanan son yaklaşımda perde arkasında kullanılır).
DRayX

1
Aslında c etiketi için geçerli olan bir Boost yanıtı ! Yaşasın!
Justin

7

Hata ayıklama yazdırmak için kullandığım çok basit bir makro:

#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'ye kaç tane argüman aktarılırsa aktarılsın c99 uyarısı yoktur.

İşin püf noktası __DBG_INT, kukla bir parametre eklemektir, bu nedenle ...her zaman en az bir argümana sahip olur ve c99 karşılanır.


Bu kodla ilgili küçük bir uyarı, yazıldığı gibi: çift alt çizgi __DBG_INT"tanımsız davranış" ile sonuçlanan bir şey olarak kabul edilir. Bu , sorun yaratma olasılığı düşük bir şeydir , ancak bir şeyleri sıfırdan yazarken veya yeniden düzenlerken bilmek iyidir - DBG_INT_veya gibi farklı bir kural seçmenin kolay olduğu durumlar DBG__INT.
chadjoan

C11 standardından (N1570) ilgili parçacıklar, 7.1.3 Ayrılmış tanımlayıcılar: "1. Bir alt çizgi ve bir büyük harf veya başka bir alt çizgi ile başlayan tüm tanımlayıcılar her zaman herhangi bir kullanım için ayrılmıştır." "2. Başka hiçbir tanımlayıcı rezerve edilmez. Program, ayrılmış olduğu bir bağlamda (7.1.4'te izin verilenin dışında) bir tanımlayıcı bildirir veya tanımlarsa veya ayrılmış bir tanımlayıcıyı bir makro adı olarak tanımlarsa, davranış tanımsızdır . " (Not: bu aynı zamanda şöyle bir şeyi de _DBG_INT
dışlar

5

Geçenlerde benzer bir sorunla karşılaştım ve bir çözüm olduğuna inanıyorum.

Temel fikir, NUM_ARGSdeğişken bir makronun verildiği argümanların sayısını saymak için bir makro yazmanın bir yolu olduğudur. Bir değişken makroya 1 bağımsız değişken mi yoksa 2 veya daha fazla bağımsız değişken mi verildiğini söyleyebilen, NUM_ARGSoluşturmak için bir varyasyonunu kullanabilirsiniz NUM_ARGS_CEILING2. Ardından Barmakronuzu kullanması için yazabilirsiniz NUM_ARGS_CEILING2veCONCAT , bağımsız değişkenlerini iki yardımcı birine göndermesi için : biri tam olarak 1 bağımsız değişken, diğeri ise 1'den büyük değişken sayıda bağımsız değişken bekliyor.

İşte makro yazmak için bu hile kullanmak bir örnek UNIMPLEMENTEDçok benzer, BAR:

AŞAMA 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

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

Adım 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__)

AŞAMA 3:

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

CONCAT'ın olağan şekilde uygulandığı yer. Hızlı bir ipucu olarak, yukarıdakiler kafa karıştırıcı görünüyorsa: CONCAT'ın amacı, başka bir makro "çağrısına" genişletmektir.

NUM_ARGS'ın kendisinin kullanılmadığını unutmayın. Buradaki temel numarayı göstermek için ekledim. Güzel bir muamele için Jens Gustedt'in P99 bloguna bakın .

İki not:

  • NUM_ARGS, işlediği bağımsız değişkenlerin sayısı bakımından sınırlıdır. Benimki, sayı tamamen keyfi olmasına rağmen, yalnızca 20'ye kadar işleyebilir.

  • NUM_ARGS, gösterildiği gibi, 0 bağımsız değişken verildiğinde 1 döndürmesi gibi bir tuzağa sahiptir. İşin özü, NUM_ARGS'nin teknik olarak bağımsız değişkenleri değil [virgül + 1] 'i saymasıdır. Bu özel durumda, aslında bizim yararımıza çalışıyor. _UNIMPLEMENTED1 boş bir jetonu gayet iyi idare eder ve bizi _UNIMPLEMENTED0 yazmak zorunda kalmaktan kurtarır. Gustedt'in bunun için de bir çözümü var, ancak kullanmadım ve burada yaptığımız şey için işe yarayıp yaramayacağından emin değilim.


Tartışma sayma hilesini ortaya çıkarmak için +1, takip etmesi gerçekten zor olduğu için -1
Richard Hansen

Eklediğiniz yorumlar bir gelişmeydi, ancak yine de bazı sorunlar var: 1. Tartışır ve tanımlarsınız NUM_ARGSama onu kullanmazsınız. 2. Amacı nedir UNIMPLEMENTED? 3. Sorudaki örnek problemi asla çözemezsiniz. 4. Genişletmede her seferinde bir adım yürümek, nasıl çalıştığını gösterir ve her yardımcı makronun rolünü açıklar. 5. 0 argüman tartışmak dikkat dağıtıcıdır; OP standartlara uyumu soruyordu ve 0 argüman yasaklandı (C99 6.10.3p4). 6. Adım 1.5? Neden 2. adım değil? 7. "Adımlar", sırayla gerçekleşen eylemleri ifade eder; bu sadece koddur.
Richard Hansen

8. İlgili gönderiye değil blogun tamamına bağlantı verirsiniz. Bahsettiğiniz gönderiyi bulamadım. 9. Son paragraf garip: Bu yöntem olup belirsiz; bu yüzden daha önce hiç kimse doğru bir çözüm göndermemişti. Ayrıca, işe yararsa ve standarda uyuyorsa, Zack'in cevabı yanlış olmalıdır. 10. Tanımlamalısınız CONCAT()- okuyucuların nasıl çalıştığını bildiğini varsaymayın.
Richard Hansen

(Lütfen bu geri bildirimi bir saldırı olarak yorumlamayın - Cevabınızı gerçekten yükseltmek istedim, ancak anlaşılması daha kolay hale getirilmedikçe bunu yapmaktan çekinmedim. Cevabınızın netliğini artırabilirseniz, seninkine oy ver ve benimkini sil.)
Richard Hansen

2
Bu yaklaşımı asla düşünmezdim ve GCC'nin mevcut ön işlemcisinin kabaca yarısını yazdım! Bununla birlikte, "bu etkiyi elde etmenin standart bir yolu yoktur" diyorum, çünkü hem sizin hem de Richard'ın teknikleri, makroya yönelik argümanların sayısına bir üst sınır koyar.
zwol

2

Bu, kullandığım basitleştirilmiş versiyon. Buradaki diğer cevapların harika tekniklerine dayanıyor, onlara pek çok destek var:

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

Bu kadar.

Diğer çözümlerde olduğu gibi bu, makrodaki argümanların sayısıyla sınırlıdır. Daha fazlasını desteklemek için _SELECT, daha fazla parametre ve daha fazla Nbağımsız değişken ekleyin . Argüman adları, sayıya dayalı SUFFIXargümanın ters sırada sağlandığını hatırlatmak için geriye doğru sayar (yukarı yerine) .

Bu çözüm, 0 bağımsız değişkeni 1 bağımsız değişkenmiş gibi ele alır. Yani BAR()sözde "işe yarar", çünkü genişleyen _SELECT(_BAR,,N,N,N,N,1)(), genişleyen _BAR_1()(), genişleyen printf("\n").

İsterseniz, _SELECTfarklı sayıda bağımsız değişken için farklı makrolar kullanarak yaratıcılığınızı konuşturabilirsiniz. Örneğin, burada formattan önce bir 'seviye' argümanı alan bir LOG makrosumuz var. Eğer format eksikse, "(mesaj yok)" günlüğünü tutar, sadece 1 argüman varsa, "% s" üzerinden günlüğe kaydeder, aksi takdirde format argümanını kalan argümanlar için bir printf format dizgesi olarak değerlendirir.

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

Bu, -pedantic ile derlendiğinde hala bir uyarı tetikler.
PSkocik

1

Durumunuza (en azından 1 argüman mevcut, asla 0) olarak, tanımlayabilirsiniz BARolarak BAR(...)kullanmak, Jens Gustedt en HAS_COMMA(...) virgül algılamak ve sonra sevk BAR0(Fmt)veyaBAR1(Fmt,...) buna göre.

Bu:

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

-pedanticuyarmadan derler .


0

C (gcc) , 762 bayt

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

Çevrimiçi deneyin!

Varsayımlar:

  • Hiçbir bağımsız değişken virgül veya köşeli ayraç içermez
  • Hiçbir argüman A~ içerir G(hard_collide olanlara yeniden adlandırılabilir)

no arg contain commaSınırlama biraz daha geçer sonra multi kontrol ederek bypass ama olabilir no brackethala orada
l4m2

-2

Standart çözüm FOOyerine kullanmaktır BAR. Muhtemelen sizin için yapamayacağı bir kaç garip argüman vakası vardır (bahse girerim ki birisi, __VA_ARGS__içindeki argümanların sayısına bağlı olarak şartlı olarak yeniden birleştirmek için zekice hackler bulabilir !) Ama genel olarak FOO"genellikle" kullanarak. sadece çalışır.


1
Soru, "bu davranışı elde etmenin standartlara uygun bir yolu var mı?"
Marsh Ray

2
Ve soru, FOO'yu çağlardır kullanmamanın bir gerekçesini içeriyor.
Pavel Šimerda
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.