Przecinek w makrze C / C ++

104

Powiedzmy, że mamy takie makro

#define FOO(type,name) type name

Którego moglibyśmy użyć

FOO(int, int_var);

Ale nie zawsze tak prosto:

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

Oczywiście mogliśmy zrobić:

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

co nie jest zbyt ergonomiczne. Niezgodności typu Plus muszą zostać rozwiązane. Masz jakiś pomysł, jak rozwiązać ten problem za pomocą makra?

Muzyka pop
źródło
Domyślam się, że musisz uciec przed postaciami, aby uczynić je dosłownymi.
Jite
Przynajmniej w C ++ możesz umieścić typedef w dowolnym miejscu, więc nie jestem pewien, dlaczego mówisz, że musi to być „z góry”.
Vaughn Cato

Odpowiedzi:

111

Ponieważ wsporniki kątowe może także oznaczać (lub występują w) operatorów porównania <, >, <=i >=, makro rozszerzeń nie ignorować przecinki wewnątrz wsporników kątowych, jak to czyni w nawiasach. (Jest to również problem w przypadku nawiasów kwadratowych i nawiasów klamrowych, mimo że zwykle występują one jako zrównoważone pary). Możesz umieścić argument makro w nawiasach:

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

Problem polega na tym, że parametr pozostaje w nawiasach wewnątrz rozwinięcia makra, co uniemożliwia odczytanie go jako typu w większości kontekstów.

Fajną sztuczką do obejścia tego jest to, że w C ++ możesz wyodrębnić nazwę typu z nazwy typu w nawiasach za pomocą typu funkcji:

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

Ponieważ tworzenie typów funkcji ignoruje dodatkowe nawiasy, możesz użyć tego makra z nawiasami lub bez, gdzie nazwa typu nie zawiera przecinka:

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

W C oczywiście nie jest to konieczne, ponieważ nazwy typów nie mogą zawierać przecinków poza nawiasami. Tak więc dla makra międzyjęzykowego możesz napisać:

#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
źródło
To jest niesamowite. Ale jak się o tym dowiedziałeś? Próbowałem wielu sztuczek i nigdy nie myślałem, że typ funkcji rozwiązuje problem.
Will Custode
@WilliamCustode, o ile pamiętam, studiowałem gramatykę typów funkcji i deklaracji funkcji w odniesieniu do najbardziej irytującego problemu z analizą, więc szczęśliwie zdawałem sobie sprawę, że w tym kontekście do typu można zastosować nadmiarowe nawiasy.
ecatmur
Znalazłem problem z tą metodą podczas pracy z szablonami. Powiedzmy, że kod, który chciałem, wyglądał następująco: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Jeśli zastosuję tutaj to rozwiązanie, struktury za makrem staną się typami zależnymi, a przedrostek nazwy typu jest teraz wymagany dla typu. Możesz go dodać, ale dedukcja typów została zerwana, więc musisz teraz ręcznie wyświetlić argumenty typu, aby wywołać funkcję. Skończyło się na użyciu metody świątyni do definiowania makra dla przecinka. Może nie wyglądać tak ładnie, ale działało idealnie.
Roger Sanders
Mały problem z odpowiedzią: stwierdza, że ​​przecinki są ignorowane w środku [] i {}, nie są, działa tylko ze ()smutkiem. Zobacz: Jednak nie ma wymogu stosowania nawiasów kwadratowych ani nawiasów klamrowych do zrównoważenia ...
VinGarcia
Niestety to nie działa w MSVC: godbolt.org/z/WPjYW8 . Wygląda na to, że MSVC nie pozwala na dodawanie wielu parenów i nie może ich przeanalizować. Rozwiązanie, które nie jest elegancka, ale szybciej (mniej matrycy dawałaby) jest owinięcie argumentu przecinkami ed na owijkę makro: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Przekazywanie argumentów jest teraz łatwo możliwe nawet przy użyciu wielu makr, a dla prostych typów można pominąć OCHRONA. Jednak typy funkcji stają się wskaźnikami funkcji, gdy są oceniane w ten sposób
Flamefire,
119

Jeśli nie możesz użyć nawiasów i nie podoba ci się rozwiązanie SINGLE_ARG autorstwa Mike'a, po prostu zdefiniuj PRZECINEK:

#define COMMA ,

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

Jest to również pomocne, jeśli chcesz zdefiniować niektóre argumenty makr, jak w

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

który drukuje std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

nie jest użytkownikiem
źródło
16
#define COMMA wow, właśnie zaoszczędziłeś mi GODZINY pracy ... dlaczego nie pomyślałem o tym lata temu. Dzięki za podzielenie się tym pomysłem. To pozwala mi nawet budować makra, które ustawiają funkcje z różnymi liczbami argumentów.
moliad
28
Plus 1 za horror
namezero
1
@kiw Jeśli ty #define STRVX(...) STRV(__VA_ARGS__)i #define STRV(...) # __VA_ARGS__, to std::cout << STRV(type<A COMMA B>) << std::endl;wydrukuje type<A COMMA B>i std::cout << STRVX(type<A COMMA B>) << std::endl;wydrukuje type<A , B>. ( STRVoznacza „variadic stringify” i STRVX„expand variadic stringify”.)
not-a-user
1
@ not-a-user tak, ale z makrami wariadycznymi nie potrzebujesz COMMAmakra w pierwszej kolejności. Na tym skończyłem.
kiw
Nigdy bym tego nie używał, ale +1 za bycie przezabawnym.
Rafael Baptista
58

Jeśli Twój preprocesor obsługuje makra wariadyczne:

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

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

W przeciwnym razie jest to trochę bardziej uciążliwe:

#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
źródło
O Boże ... Dlaczego? Dlaczego nie ująć po prostu w nawiasach?
15
@VladLazarenko: Ponieważ nie możesz zawsze umieszczać dowolnych fragmentów kodu w nawiasach. W szczególności nie można umieszczać nawiasów wokół nazwy typu w deklaratorze, co jest dokładnie tym, czym staje się ten argument.
Mike Seymour
2
... a także dlatego, że możesz modyfikować tylko definicję makra, a nie wszystkie miejsca, które ją wywołują (co może nie być pod Twoją kontrolą lub może być rozłożone na tysiące plików itp.). Dzieje się tak na przykład podczas dodawania makra, które przejmuje obowiązki funkcji o podobnej nazwie.
BeeOnRope
32

Po prostu zdefiniuj FOOjako

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Następnie wywołaj go zawsze z nawiasami wokół argumentu typu, np

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

Oczywiście dobrym pomysłem może być zilustrowanie wywołań w komentarzu do definicji makra.

Pozdrawiam i hth. - Alf
źródło
Nie wiem, dlaczego jest to tak daleko, to o wiele lepsze rozwiązanie niż Mike Seymours. Jest to szybkie i proste oraz całkowicie ukryte przed użytkownikiem.
iFreilicht
3
@iFreilicht: Opublikowano nieco ponad rok później. ;-)
Pozdrawiam i hth. - Alf
5
A ponieważ również trudno jest zrozumieć, jak i dlaczego to działa
VinGarcia
@VinGarcia, możesz wyjaśnić, dlaczego / jak to działa? Dlaczego przy jego wywoływaniu wymagane są nawiasy? Co UNPACKprzy takim użyciu ) UNPACK type name? Dlaczego typepoprawnie pobiera typ, gdy jest używany ) UNPACK type name? Tylko co tu się do cholery dzieje?
użytkownik
Nie @user, może Cheers i hth mogą ci odpowiedzieć
VinGarcia,
4

Można to zrobić na co najmniej dwa sposoby. Najpierw możesz zdefiniować makro, które przyjmuje wiele argumentów:

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

jeśli to zrobisz, może się okazać, że w końcu zdefiniujesz więcej makr do obsługi większej liczby argumentów.

Po drugie, możesz umieścić argument w nawiasach:

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

jeśli to zrobisz, może się okazać, że dodatkowe nawiasy zepsują składnię wyniku.

Pete Becker
źródło
W przypadku pierwszego rozwiązania każde makro będzie musiało mieć inną nazwę, ponieważ makra nie są przeciążane. Po drugie, jeśli przekazujesz nazwę typu, istnieje bardzo duża szansa, że ​​zostanie ona użyta do zadeklarowania zmiennej (lub typedef), więc nawiasy będą powodować problemy.
James Kanze
4

Jest to możliwe z P99 :

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

Powyższy kod skutecznie usuwa tylko ostatni przecinek na liście argumentów. Sprawdź za pomocą clang -E(P99 wymaga kompilatora C99).

xiaq
źródło
3

Prosta odpowiedź brzmi: nie możesz. Jest to efekt uboczny wyboru <...>argumentów dla szablonu; <a >także pojawiają się w kontekstach niesymetrycznych więc makro mechanizm nie mógł być przedłużony do obsługi ich jak obsługuje nawiasów. (Niektórzy członkowie komitetu opowiadali się za innym tokenem, powiedzmy (^...^), ale nie byli w stanie przekonać większości problemów za pomocą <...>.)

James Kanze
źródło
2
(^...^)to jedna szczęśliwa buźka :)
CygnusX1