Foreach-Makro auf Makroargumente

Lesezeit: 14 Minuten

Ich frage mich, ob es möglich ist, ein Makro foreach für Makroargumente zu schreiben. Hier ist, was Sie tun möchten:

#define PRINT(a) printf(#a": %d", a)
#define PRINT_ALL(...) ? ? ? THE PROBLEM ? ? ? 

Und mögliche Verwendung:

int a = 1, b = 3, d = 0;
PRINT_ALL(a,b,d);

Hier ist, was ich bisher erreicht habe

#define FIRST_ARG(arg,...) arg
#define AFTER_FIRST_ARG(arg,...) , ##__VA_ARGS__     
#define PRINT(a) printf(#a": %d", a)
#define PRINT_ALL PRINT(FIRST_ARG(__VA_ARGS__)); PRINT_ALL(AFTER_FIRST_ARG(__VA_ARGS__))

Dies ist ein rekursives Makro, das illegal ist. Und ein weiteres Problem damit ist stop condition der Rekursion.

  • ## in AFTER_FIRST_ARG ist eine GNU-Compiler-Erweiterung von C

    – kokosing

    15. Juli 2011 um 12:48 Uhr

  • Siehe stackoverflow.com/questions/824639/…

    – sehen

    15. Juli 2011 um 13:11 Uhr

  • Sie können dieses Problem in C++ auch mit dem Komma-Operator auf schönere Weise lösen.

    – Johannes Schaub – litb

    15. Juli 2011 um 13:31 Uhr

  • @Johannes: Ohne die Zeichenfolgenvariablennamen (oder Ausdrücke oder …).

    – Georg Fritzsche

    15. Juli 2011 um 13:38 Uhr

Benutzer-Avatar
William Swanson

Ja, rekursive Makros sind in C mit einem ausgefallenen Workaround möglich. Das Endziel ist die Erstellung eines MAP Makro, das so funktioniert:

#define PRINT(a) printf(#a": %d", a)
MAP(PRINT, a, b, c) /* Apply PRINT to a, b, and c */

Einfache Rekursion

Zuerst brauchen wir eine Technik, um etwas auszugeben, das wie ein Makroaufruf aussieht, es aber noch nicht ist:

#define MAP_OUT

Stellen Sie sich vor, wir haben die folgenden Makros:

#define A(x) x B MAP_OUT (x)
#define B(x) x A MAP_OUT (x)

Auswertung des Makros A (blah) erzeugt den Ausgabetext:

blah B (blah)

Der Präprozessor sieht keine Rekursion, da die B (blah) call ist an dieser Stelle nur Klartext, und B ist nicht einmal der Name des aktuellen Makros. Das Zurückführen dieses Textes in den Präprozessor erweitert den Aufruf und erzeugt die Ausgabe:

blah blah A (blah)

Eine dritte Auswertung der Ausgabe erweitert die A (blah) Makro, das den Rekursionskreis trägt. Die Rekursion wird fortgesetzt, solange der Aufrufer den Ausgabetext weiterhin in den Präprozessor einspeist.

Um diese wiederholten Bewertungen durchzuführen, das Folgende EVAL Makro übergibt seine Argumente an einen Baum von Makroaufrufen:

#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0 (EVAL0 (EVAL0 (__VA_ARGS__)))
#define EVAL2(...) EVAL1 (EVAL1 (EVAL1 (__VA_ARGS__)))
#define EVAL3(...) EVAL2 (EVAL2 (EVAL2 (__VA_ARGS__)))
#define EVAL4(...) EVAL3 (EVAL3 (EVAL3 (__VA_ARGS__)))
#define EVAL(...)  EVAL4 (EVAL4 (EVAL4 (__VA_ARGS__)))

Jedes Level multipliziert den Aufwand des vorherigen Levels und wertet die Eingabe insgesamt 365 Mal aus. Mit anderen Worten, anrufen EVAL (A (blah)) würde 365 Kopien des Wortes produzieren blahgefolgt von einem letzten unbewerteten B (blah). Dies liefert zumindest innerhalb einer bestimmten Stack-Tiefe das Grundgerüst für die Rekursion.

Erkennung beenden

Die nächste Herausforderung besteht darin, die Rekursion zu stoppen, wenn sie das Ende der Liste erreicht.

Die Grundidee besteht darin, anstelle des normalen rekursiven Makros den folgenden Makronamen auszugeben, wenn die Zeit zum Beenden gekommen ist:

#define MAP_END(...)

Das Auswerten dieses Makros bewirkt nichts, wodurch die Rekursion beendet wird.

Um tatsächlich zwischen den beiden Makros zu wählen, folgendes MAP_NEXT
-Makro vergleicht ein einzelnes Listenelement mit der speziellen Listenende-Markierung
(). Das Makro kehrt zurück MAP_END wenn der Artikel übereinstimmt, oder die next
Parameter, wenn das Element etwas anderes ist:

#define MAP_GET_END() 0, MAP_END
#define MAP_NEXT0(item, next, ...) next MAP_OUT
#define MAP_NEXT1(item, next) MAP_NEXT0 (item, next, 0)
#define MAP_NEXT(item, next)  MAP_NEXT1 (MAP_GET_END item, next)

Dieses Makro funktioniert, indem das Element neben dem platziert wird MAP_GET_END Makro. Wenn dies einen Makroaufruf bildet, wird alles um einen Slot in verschoben
MAP_NEXT0 Parameterliste, Änderung der Ausgabe. Das MAP_OUT Trick verhindert, dass der Präprozessor das Endergebnis auswertet.

Alles zusammenfügen

Mit diesen Teilen ist es nun möglich, nützliche Versionen von zu implementieren A und B Makros aus obigem Beispiel:

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__)

Diese Makros wenden die Operation an f zum aktuellen Listeneintrag x. Dann untersuchen sie den nächsten Listenpunkt, peekum zu sehen, ob sie fortfahren sollen oder nicht.

Der letzte Schritt besteht darin, alles auf einer obersten Ebene zusammenzufassen MAP Makro:

#define MAP(f, ...) EVAL (MAP1 (f, __VA_ARGS__, (), 0))

Dieses Makro platziert a () Markierung am Ende der Liste, sowie ein Extra
0 für ANSI-Konformität (andernfalls hätte die letzte Iteration eine unzulässige Liste der Länge 0). Dann durchläuft er das Ganze EVAL und gibt das Ergebnis zurück.

Ich habe diesen Code als hochgeladen Bibliothek auf github Für Ihren Komfort.

  • Sehr schön. Aber ist es möglich, einen Wert zusammen mit weiterzugeben VA_ARGS zur Zielfunktion? Ich habe es geschafft, 1 Parameter hinzuzufügen, aber als ich versuche, ein paar Parameter in diesen zu packen, bin ich hängen geblieben. coliru.stacked-crooked.com/a/8622a9a5fb7d2ba5

    – Turm120

    1. August 2015 um 11:52 Uhr

  • Damit dies mit VS2015 in meinem Setup funktioniert, musste ich Folgendes tun: #define MAP_NEXT1(item, next) EVAL0(MAP_NEXT0 (item, next, 0))

    – sekundär

    8. Juni 2016 um 12:26 Uhr

  • Das war sehr nützlich für mich; Ich habe hier eine verwandte (Folge-)Frage.

    – einpoklum

    17. Juni 2016 um 11:45 Uhr

  • Dies funktioniert wunderbar, außer wenn Sie einen Fehler in der Makroerweiterung erhalten und Sie ewig nach oben scrollen müssen, um alle Infos zu überspringen, die es durch alle EVAL-Erweiterungen zurückverfolgen.

    – Chris Smith

    24. April 2017 um 16:32 Uhr

  • Woher kommt die Zahl 365?

    – JoaoBapt

    21. November 2020 um 19:09 Uhr

Verwenden PPNARG, habe ich eine Reihe von Makros geschrieben, um ein Makro auf jedes Argument in einem Makro anzuwenden. Ich nenne es ein variadisches X-Makro.

/*
 * The PP_NARG macro evaluates to the number of arguments that have been
 * passed to it.
 *
 * Laurent Deniau, "__VA_NARG__," 17 January 2006, <comp.std.c> (29 November 2007).
 */
#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

PPNARG lässt uns zählen, wie viele Argumente es gibt. Dann hängen wir diese Nummer an den Makronamen an und rufen es mit den ursprünglichen Argumenten auf.

/* need extra level to force extra eval */
#define Paste(a,b) a ## b
#define XPASTE(a,b) Paste(a,b)


/* APPLYXn variadic X-Macro by M Joshua Ryan      */
/* Free for all uses. Don't be a jerk.            */
/* I got bored after typing 15 of these.          */
/* You could keep going upto 64 (PPNARG's limit). */
#define APPLYX1(a)           X(a)
#define APPLYX2(a,b)         X(a) X(b)
#define APPLYX3(a,b,c)       X(a) X(b) X(c)
#define APPLYX4(a,b,c,d)     X(a) X(b) X(c) X(d)
#define APPLYX5(a,b,c,d,e)   X(a) X(b) X(c) X(d) X(e)
#define APPLYX6(a,b,c,d,e,f) X(a) X(b) X(c) X(d) X(e) X(f)
#define APPLYX7(a,b,c,d,e,f,g) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g)
#define APPLYX8(a,b,c,d,e,f,g,h) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h)
#define APPLYX9(a,b,c,d,e,f,g,h,i) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i)
#define APPLYX10(a,b,c,d,e,f,g,h,i,j) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j)
#define APPLYX11(a,b,c,d,e,f,g,h,i,j,k) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k)
#define APPLYX12(a,b,c,d,e,f,g,h,i,j,k,l) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l)
#define APPLYX13(a,b,c,d,e,f,g,h,i,j,k,l,m) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m)
#define APPLYX14(a,b,c,d,e,f,g,h,i,j,k,l,m,n) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m) X(n)
#define APPLYX15(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o) \
    X(a) X(b) X(c) X(d) X(e) X(f) X(g) X(h) X(i) X(j) X(k) X(l) X(m) X(n) X(o)
#define APPLYX_(M, ...) M(__VA_ARGS__)
#define APPLYXn(...) APPLYX_(XPASTE(APPLYX, PP_NARG(__VA_ARGS__)), __VA_ARGS__)

Und hier sind einige Beispiele mit der Ausgabe von gcc -E in Kommentaren.

/* Example */
#define X(a) #a,
char *list[] = {
    APPLYXn(sugar,coffee,drink,smoke)
};
#undef X

/* Produces (gcc -E)
char *list[] = {
    "sugar", "coffee", "drink", "smoke",
};
 */


#define c1(a) case a:
#define c2(a,b)     c1(a) c1(b)
#define c3(a,b,c)   c1(a) c2(b,c)
#define c4(a,b,c,d) c1(a) c3(b,c,d)
#define c_(M, ...) M(__VA_ARGS__)
#define cases(...) c_(XPASTE(c, PP_NARG(__VA_ARGS__)), __VA_ARGS__)


//cases(3,4,5,6,7)
//produces
//case 3: case 4: case 5: case 6:


#define r_(a,b) range(a,b)
#define range(a,b) a,r_(a+1,b-1)
//range(3,4)

#define ps1(a) O ## a ();
#define ps2(a,b)     ps1(a) ps1(b)
#define ps3(a,b,c)   ps1(a) ps2(b,c)
#define ps4(a,b,c,d) ps1(a) ps3(b,c,d)
#define ps_(M, ...) M(__VA_ARGS__)
#define ps(...)     ps_(XPASTE(ps, PP_NARG(__VA_ARGS__)), __VA_ARGS__)

//ps(dup,add,sub)

Letzteres war das Motiv für das Ganze. Aber es stellte sich als nicht sehr nützlich heraus.

  • Ich bin immer noch von meiner gekitzelt jugendlich/arrogant “Urheberrechtshinweis”. 🙂

    – Luser Drog

    22. Februar 2014 um 7:45 Uhr

  • Aufgrund der Veröffentlichung auf StackOverflow ist dieser Code gemeinfrei, richtig? … scheint es eine automatische Creative-Commons-Lizenz zu geben meta.stackexchange.com/questions/12527/…

    – David Dopson

    14. Februar 2018 um 20:38 Uhr


  • ps, in meiner jugendlichen/arroganten Zeit habe ich verwendet wtfpl.net/about. LawyerCats verbiegen sich alle darüber, dass b / c nicht ausdrücklich sagt, dass Sie den Code kopieren können.

    – David Dopson

    14. Februar 2018 um 20:41 Uhr

  • Technisch ist es CC von SA. Fast dasselbe wie PD, erfordert jedoch eine Zuordnung.

    – Luser Drog

    14. Februar 2018 um 20:42 Uhr

Da Sie akzeptieren, dass der Präprozessor VA_ARGS hat (in C99, aber nicht im aktuellen C++-Standard), können Sie mit gehen P99. Es hat genau das, wonach Sie fragen: P99_FOR. Es funktioniert ohne Rohöl ()()() Syntax von BOOST. Die Schnittstelle ist einfach

P99_FOR(NAME, N, OP, FUNC,...) 

und Sie können es mit etwas wie verwenden

#define P00_SEP(NAME, I, REC, RES) REC; RES
#define P00_VASSIGN(NAME, X, I) X = (NAME)[I]
#define MYASSIGN(NAME, ...) P99_FOR(NAME, P99_NARG(__VA_ARGS__), P00_SEP, P00_VASSIGN, __VA_ARGS__)

MYASSIGN(A, toto, tutu);

  • Oh, das Roh ,,, Syntax 😉

    – Georg Fritzsche

    15. Juli 2011 um 13:25 Uhr

  • @Georg 🙂 Während die Definition von Makros manchmal seltsam aussehen mag, sollte die Verwendung von Makros einfach den üblichen Konventionen der Sprache(n) folgen, denke ich.

    – Jens Gustedt

    15. Juli 2011 um 13:33 Uhr

  • Nur ein Scherz, konnte nicht widerstehen, das Boost-Wortspiel zu kommentieren 🙂 Ich denke, ich habe mich aber daran gewöhnt.

    – Georg Fritzsche

    15. Juli 2011 um 13:35 Uhr


  • Das Beispiel ist eigentlich nicht so eindeutig. Könnten Sie es ändern, um das angeforderte OP von printf auszuführen?

    – einpoklum

    11. April 2016 um 11:32 Uhr

Benutzer-Avatar
Georg Fritzsche

In C++ ohne Erweiterungen könnte man gehen Boost.Präprozessor und seine Sequenzen:

PRINT_ALL((a)(b)(c));

Durch die Nutzung BOOST_PP_SEQ_FOR_EACH() In der Sequenz können Sie sie iterieren und einfach Code generieren, der sie druckt.

Ungetestetes einfaches Beispiel:

#define DO_PRINT(elem) std::cout << BOOST_PP_STRINGIZE(elem) << "=" << (elem) << "\n";
#define PRINT_ALL(seq) { BOOST_PP_SEQ_FOR_EACH(DO_PRINT, _, seq) }

Alte Frage, aber ich dachte, ich würde eine Lösung anbringen, die ich mir ausgedacht habe, um Boost.Preprocessor ohne das Hässliche zu verwenden (a)(b) Syntax.

Header:

#include <iostream>
#include <boost\preprocessor.hpp>

#define _PPSTUFF_OUTVAR1(_var) BOOST_PP_STRINGIZE(_var) " = " << (_var) << std::endl
#define _PPSTUFF_OUTVAR2(r, d, _var) << _PPSTUFF_OUTVAR1(_var) 
#define _PPSTUFF_OUTVAR_SEQ(vseq) _PPSTUFF_OUTVAR1(BOOST_PP_SEQ_HEAD(vseq)) \
        BOOST_PP_SEQ_FOR_EACH(_PPSTUFF_OUTVAR2,,BOOST_PP_SEQ_TAIL(vseq)) 
#define OUTVAR(...) _PPSTUFF_OUTVAR_SEQ(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

Verwendungszweck:

int a = 3;
char b[] = "foo";

std::cout << OUTVAR(a);

// Expands to: 
//
// std::cout << "a" " = " << (a ) << std::endl  ;
//
// Output:
//
// a = 3

std::cout << OUTVAR(a, b);

// Expands to: 
//
// std::cout << "a" " = " << (a ) << std::endl << "b" " = " << (b) << std::endl  ;
//
// Output:
//
// a = 3
// b = foo

Schön und sauber.

Natürlich kann man die ersetzen std::endl mit einem Komma oder so, wenn Sie alles in einer Zeile haben möchten.

Sie können Boost.PP verwenden (nach dem Hinzufügen Schub‘s boost Ordner zu Ihrer Liste der Include-Verzeichnisse hinzufügen), um Makros dafür zu erhalten. Hier ist ein Beispiel (getestet mit GCC 8.1.0):

#include <iostream>
#include <limits.h>
#include <boost/preprocessor.hpp>

#define WRITER(number,middle,elem) std::cout << \
    number << BOOST_PP_STRINGIZE(middle) << elem << "\n";
#define PRINT_ALL(...) \
    BOOST_PP_SEQ_FOR_EACH(WRITER, =>, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

int main (int argc, char *argv[])
{
    PRINT_ALL(INT_MAX, 123, "Hello, world!");
}

Ausgabe:

2=>2147483647
3=>123
4=>Hello, world!

Das BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) part wandelt die Variablenargumentliste in die traditionelle Art von Boost um, mehrere Argumente als ein einziges Argument auszudrücken, was wie folgt aussieht: (item1)(item2)(item3).

Ich bin mir nicht sicher, warum die Argumente bei zwei nummeriert werden. Das Dokumentation beschreibt den ersten Parameter lediglich als „den nächsten verfügbaren BOOST_PP_FOR Wiederholung”.

Hier ist ein weiteres Beispiel, das an definiert enum mit der Fähigkeit, es als Zeichenfolge in eine zu schreiben ostreamwas auch Boosts aktiviert lexical_cast<string>:

#define ENUM_WITH_TO_STRING(ENUMTYPE, ...)                   \
    enum ENUMTYPE {                                          \
        __VA_ARGS__                                          \
    };                                                       \
    inline const char* to_string(ENUMTYPE value) {           \
        switch (value) {                                     \
            BOOST_PP_SEQ_FOR_EACH(_ENUM_TO_STRING_CASE, _,   \
               BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))        \
            default: return nullptr;                         \
        }                                                    \
    }                                                        \
    inline std::ostream& operator<<(std::ostream& os, ENUMTYPE v)\
        { return os << to_string(v); }
#define _ENUM_TO_STRING_CASE(_,__,elem)                      \
    case elem: return BOOST_PP_STRINGIZE(elem);

ENUM_WITH_TO_STRING(Color, Red, Green, Blue)

int main (int argc, char *argv[])
{
    std::cout << Red << Green << std::endl;
    std::cout << boost::lexical_cast<string>(Blue) << std::endl;
}

Ausgabe:

RedGreen
Blue

Benutzer-Avatar
Friedrich Rabe

Der Präprozessor ist nicht leistungsfähig genug, um solche Dinge zu tun. Allerdings braucht man den Präprozessor nicht unbedingt. Wenn Sie nur Variablennamen und ihre Werte auf bequeme Weise ausgeben möchten. Sie könnten zwei einfache Makros haben:

#define PRINT(x) \
{ \
    std::ostringstream stream; \
    stream << x; \
    std::cout << stream.str() << std::endl; \
}

#define VAR(v) #v << ": " << v << ", "

Sie könnten dann fast Ihre beabsichtigte Verwendung verwenden:

int a = 1, b = 3, d = 0;
PRINT(VAR(a) << VAR(b) << VAR(d))

Das druckt

a: 1, b: 3, d: 0,

Es gibt viele Möglichkeiten, dies leistungsfähiger zu machen, aber das funktioniert, ermöglicht es Ihnen, nicht ganzzahlige Werte gut zu drucken, und es ist eine ziemlich einfache Lösung.

  • Das PP ist mächtig genug, zB durch Verwendung von Boost.PP-Sequenzen in C++.

    – Georg Fritzsche

    15. Juli 2011 um 13:00 Uhr


  • Das Drucken von Variablennamen ist nur ein Teil dessen, was ich tun möchte. Ich habe eher über eine allgemeine Idee nachgedacht, ob es möglich ist, ein solches Makro zu erstellen. Es kann aus verschiedenen Gründen nützlich sein: Erstellen einer Anzahl von beispielsweise Ausnahmeklassen oder Erstellen einer Anzahl von Variablen und deren Initialisierung. Es gibt viele Möglichkeiten.

    – kokosing

    15. Juli 2011 um 13:05 Uhr

1013200cookie-checkForeach-Makro auf Makroargumente

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy