Komma im C / C ++ - Makro

103

Angenommen, wir haben ein solches Makro

#define FOO(type,name) type name

Was wir gerne gebrauchen könnten

FOO(int, int_var);

Aber nicht immer so einfach:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Natürlich könnten wir tun:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

das ist nicht sehr ergonomisch. Plus-Typ-Inkompatibilitäten müssen behoben werden. Irgendeine Idee, wie man das mit einem Makro löst?

Pop
quelle
Ich vermute, Sie müssen Zeichen mit einer Bedeutung entkommen, um sie wörtlich zu machen.
Jite
Zumindest in C ++ können Sie ein typedef überall einfügen, daher bin ich mir nicht sicher, warum Sie sagen, dass es "vorher" sein muss.
Vaughn Cato

Antworten:

108

Da Klammern Winkel auch (oder treten in) die Vergleichsoperatoren darstellen können <, >, <=und >=können Makroerweiterung ignorieren keine Kommas innerhalb spitzen Klammern , wie es in Klammern der Fall ist. (Dies ist auch ein Problem für eckige Klammern und Klammern, obwohl diese normalerweise als ausgeglichene Paare auftreten.) Sie können das Makroargument in Klammern setzen:

FOO((std::map<int, int>), map_var);

Das Problem ist dann, dass der Parameter innerhalb der Makroerweiterung in Klammern bleibt, wodurch verhindert wird, dass er in den meisten Kontexten als Typ gelesen wird.

Ein guter Trick, um dies zu umgehen, besteht darin, dass Sie in C ++ einen Typnamen aus einem in Klammern gesetzten Typnamen mithilfe eines Funktionstyps extrahieren können:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Da beim Bilden von Funktionstypen zusätzliche Klammern ignoriert werden, können Sie dieses Makro mit oder ohne Klammern verwenden, wenn der Typname kein Komma enthält:

FOO((int), int_var);
FOO(int, int_var2);

In C ist dies natürlich nicht erforderlich, da Typnamen keine Kommas außerhalb von Klammern enthalten dürfen. Für ein sprachübergreifendes Makro können Sie also schreiben:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif
ecatmur
quelle
Das ist fantastisch. Aber wie haben Sie davon erfahren? Ich habe unzählige Tricks ausprobiert und nie gedacht, dass ein Funktionstyp das Problem beheben würde.
Will Custode
@WilliamCustode Ich erinnere mich, dass ich die Grammatik von Funktionstypen und Funktionsdeklarationen unter Bezugnahme auf das ärgerlichste Analyseproblem untersucht hatte. Daher war es ein Zufall, dass mir bewusst war, dass redundante Klammern auf einen Typ in diesem Kontext angewendet werden können.
Ecatmur
Ich habe ein Problem mit dieser Methode beim Arbeiten mit Vorlagen gefunden. template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Angenommen, der gewünschte Code war folgender: Wenn ich diese Lösung hier anwende, werden die Strukturen hinter dem Makro zu abhängigen Typen, und das Typnamenpräfix ist jetzt für den Typ erforderlich. Sie können es hinzufügen, aber der Typabzug wurde unterbrochen, sodass Sie jetzt die Typargumente manuell auflisten müssen, um die Funktion aufzurufen. Am Ende habe ich die Methode von Temple verwendet, um ein Makro für das Komma zu definieren. Es sieht vielleicht nicht so hübsch aus, aber es hat perfekt funktioniert.
Roger Sanders
Ein kleines Problem bei der Antwort: Es heißt, dass Kommas im Inneren ignoriert werden [] und {}es funktioniert ()leider nur mit . Siehe: Es ist jedoch nicht erforderlich, dass eckige Klammern oder Klammern ausgeglichen sind ...
VinGarcia
Leider funktioniert dies in MSVC nicht: godbolt.org/z/WPjYW8 . Es scheint, dass MSVC das Hinzufügen mehrerer Parens nicht zulässt und diese nicht analysiert. Eine Lösung, die nicht so elegant, aber schneller ist (weniger Vorlageninstanziierungen), besteht darin, das Komma-Argument in ein Wrapper-Makro zu packen : #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Das Übergeben von Argumenten ist jetzt auch über mehrere Makros problemlos möglich. Für einfache Typen können Sie den PROTECT weglassen. Funktionstypen werden jedoch zu Funktionszeigern, wenn sie wie
folgt
119

Wenn Sie keine Klammern verwenden können und Mikes SINGLE_ARG-Lösung nicht mögen, definieren Sie einfach ein COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

Dies ist auch hilfreich, wenn Sie einige der Makroargumente wie in

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

welche druckt std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

kein Benutzer
quelle
16
#define COMMA wow, du hast mir gerade STUNDEN Arbeit erspart ... warum habe ich vor Jahren nicht daran gedacht. Vielen Dank, dass Sie diese Idee geteilt haben. Auf diese Weise kann ich sogar Makros erstellen, die Funktionen mit unterschiedlichen Argumenten einrichten.
Moliade
28
Plus 1 für den Horror
Namezero
1
@kiw Wenn Sie #define STRVX(...) STRV(__VA_ARGS__)und #define STRV(...) # __VA_ARGS__, dann std::cout << STRV(type<A COMMA B>) << std::endl;wird gedruckt type<A COMMA B>und std::cout << STRVX(type<A COMMA B>) << std::endl;wird gedruckt type<A , B>. ( STRVist für "variadic stringify" und STRVXist für "erweitertes variadic stringify".)
kein Benutzer
1
@ not-a-user ja, aber mit variadischen Makros brauchen Sie das COMMAMakro überhaupt nicht . Damit bin ich gelandet.
Kiw
Ich würde das nie benutzen, aber +1, um lustig zu sein.
Rafael Baptista
58

Wenn Ihr Präprozessor verschiedene Makros unterstützt:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

Ansonsten ist es etwas langweiliger:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);
Mike Seymour
quelle
Oh mein Gott ... Warum? Warum nicht einfach in Klammern setzen?
15
@VladLazarenko: Weil man nicht immer beliebige Codeteile in Klammern setzen kann. Insbesondere können Sie den Typnamen in einem Deklarator nicht in Klammern setzen. Genau das wird dieses Argument.
Mike Seymour
2
... und auch , weil Sie nur in der Lage sein können , um das Makro zu ändern Definition und nicht alle Orte , die es nennen (die unter Ihrer Kontrolle möglicherweise nicht oder kann verteilt über 1000s Dateien usw. sein). Dies tritt beispielsweise auf, wenn ein Makro hinzugefügt wird, um Aufgaben von einer gleichnamigen Funktion zu übernehmen.
BeeOnRope
31

Einfach definieren FOOals

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Rufen Sie es dann immer in Klammern um das Typargument auf, z

FOO( (std::map<int, int>), map_var );

Es kann natürlich eine gute Idee sein, die Aufrufe in einem Kommentar zur Makrodefinition zu veranschaulichen.

Prost und hth. - Alf
quelle
Ich bin mir nicht sicher, warum dies so weit unten liegt, es ist eine viel schönere Lösung als Mike Seymours. Es ist schnell und einfach und dem Benutzer völlig verborgen.
iFreilicht
3
@iFreilicht: Es wurde etwas mehr als ein Jahr später veröffentlicht. ;-)
Prost und hth. - Alf
5
Und weil es auch schwer zu verstehen ist, wie und warum es funktioniert
VinGarcia
@ VinGarcia, können Sie erklären, warum / wie es funktioniert? Warum werden beim Aufruf die Klammern benötigt? Was UNPACKtun, wenn es so verwendet wird ) UNPACK type name? Warum typewird der Typ bei Verwendung korrekt abgerufen ) UNPACK type name? Was zum Teufel ist hier los?
Benutzer
Kein @user, vielleicht können Cheers und hth Ihnen antworten
VinGarcia
4

Es gibt mindestens zwei Möglichkeiten, dies zu tun. Zunächst können Sie ein Makro definieren, das mehrere Argumente akzeptiert:

#define FOO2(type1, type2, name) type1, type2, name

Wenn Sie dies tun, werden Sie möglicherweise mehr Makros definieren, um mehr Argumente zu verarbeiten.

Zweitens können Sie das Argument in Klammern setzen:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

Wenn Sie dies tun, stellen Sie möglicherweise fest, dass die zusätzlichen Klammern die Syntax des Ergebnisses durcheinander bringen.

Pete Becker
quelle
Für die erste Lösung muss jedes Makro einen anderen Namen haben, da Makros nicht überladen werden. Und zum zweiten, wenn Sie einen Typnamen übergeben, besteht eine sehr gute Chance, dass er zum Deklarieren einer Variablen (oder eines Typedef) verwendet wird, sodass die Klammern Probleme verursachen.
James Kanze
4

Dies ist mit P99 möglich :

#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()

Der obige Code entfernt effektiv nur das letzte Komma in der Argumentliste. Überprüfen Sie mit clang -E(P99 erfordert einen C99-Compiler).

xiaq
quelle
3

Die einfache Antwort ist, dass Sie nicht können. Dies ist ein Nebeneffekt der Auswahl von <...>Vorlagenargumenten. Das <und wird >auch in unausgeglichenen Kontexten angezeigt, sodass der Makromechanismus nicht so erweitert werden kann, dass er sie wie Klammern behandelt. (Einige der Komiteemitglieder hatten sich beispielsweise für ein anderes Zeichen ausgesprochen (^...^), konnten jedoch die Mehrheit der Probleme nicht überzeugen <...>.)

James Kanze
quelle
2
(^...^)Dies ist ein glückliches Gesicht :)
CygnusX1