2012-12-12 14 views
72

że mamy makro jak tenPrzecinek w C/C++ makro

#define FOO(type,name) type name 

które mogliśmy wykorzystać jak

FOO(int, int_var); 

Ale nie zawsze tak prosto jak:

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

Oczywiście moglibyśmy:

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

który nie jest zbyt ergonomiczny. Należy rozwiązywać niezgodności typu plus. Każdy pomysł, jak rozwiązać ten problem z makrem?

+0

Zgaduję, że musisz uciec od postaci, które mają znaczenie, aby były literałami. – Jite

+0

Przynajmniej w C++, możesz umieścić typedef wszędzie, więc nie jestem pewien, dlaczego mówisz, że musi być "wcześniej". –

Odpowiedz

78

Ponieważ nawiasy kątowe mogą również reprezentować (lub występować w) porównanie Operatory <, >, <= i >=, rozszerzenie makr nie może zignorować przecinków w nawiasach ostrych, podobnie jak w nawiasach. (Jest to również problem dla nawiasach kwadratowych i szelki, chociaż te występują zwykle jako wyważone par.) Można załączyć makro argument w nawiasach:

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

Problem jest wtedy, że parametr pozostaje w nawiasach wewnątrz makra rozszerzenie, które uniemożliwia odczytanie go jako typ w większości kontekstów.

Miła trik obejścia tego jest to, że w języku C++, można wyodrębnić TypeName od A nawiasach nazwy typu użyciu funkcja Typ:

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ż tworząc typy funkcyjne ignoruje dodatkowych nawiasów, można użyć tego makra z lub bez nawiasów gdzie nazwa typu nie zawiera przecinek:

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

w C, oczywiście, nie jest to konieczne, ponieważ nazwy typu nie może zawierać przecinki poza nawiasami. Tak więc, na makro cross-językowego można 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 
+0

To jest niesamowite. Ale jak się o tym dowiedziałeś? Próbowałem mnóstwo sztuczek i nawet nie myślałem, że typ funkcji naprawi problem. –

+0

@WilliamCustode, jak pamiętam, badałem gramatykę typów funkcji i deklaracji funkcji w odniesieniu do najbardziej irytującego problemu parsowania, więc było przypadkiem, że zdawałem sobie sprawę, że nadmiarowe nawiasy można zastosować do typu w tym kontekście. – ecatmur

+0

Znalazłem problem z tą metodą podczas pracy z szablonami.Powiedzmy, że kod, który chciałem, był następujący: 'template void SomeFunc (FOO (std :: map ) element) {}' Jeśli zastosuję to rozwiązanie tutaj, struktura za makrem stają się typy zależne, a prefiks typu jest teraz wymagany na typie. Możesz go dodać, ale odliczenie typu zostało zerwane, więc musisz teraz ręcznie wpisać argumenty typu, aby wywołać funkcję. Skończyło się na stosowaniu przez świątynię metody definiowania makra na przecinek. Może nie wygląda tak ładnie, ale działało idealnie. –

38

Jeśli preprocesor obsługuje zmiennej liczbie argumentów makra:

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

Och, Boże ... Dlaczego? Dlaczego po prostu nie umieścić w nawiasach? –

+11

@ VladLazarenko: Ponieważ nie zawsze można umieścić dowolne fragmenty kodu w nawiasach. W szczególności nie można umieścić nawiasów wokół nazwy typu w deklaratorze, co jest dokładnie tym, czym staje się ten argument. –

+1

... a także dlatego, że możesz zmodyfikować makro _definition_, a nie wszystkie miejsca, które go nazywają (które mogą nie być pod Twoją kontrolą, lub mogą być rozłożone na tysiące plików itp.). Dzieje się tak na przykład podczas dodawania makra do przejęcia obowiązków z funkcji o tej samej nazwie. – BeeOnRope

2

Istnieją co najmniej dwa sposoby, aby to zrobić. Po pierwsze, można zdefiniować makro, które ma wiele argumentów:

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

jeśli to zrobisz może się okazać, że w końcu definiując kolejne makr obsłużyć więcej argumentów.

Po drugie, można umieścić nawiasy wokół argumentu:

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

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

+0

Dla pierwszego rozwiązania każde makro będzie musiało mieć inną nazwę, ponieważ makra nie przeciążają. I na drugi, jeśli podajesz nazwę typu, istnieje bardzo duża szansa, że ​​zostanie użyta do zadeklarowania zmiennej (lub typedef), więc nawiasy będą powodować problemy. –

1

Prosta odpowiedź brzmi, że nie możesz. Jest to efekt uboczny wyboru <...> dla argumentów szablonu; < i > pojawiają się także w niezbalansowanych kontekstach, więc nie można rozszerzyć mechanizmu makro tak, aby obsługiwał je tak, jak obsługuje nawiasy. (Niektórzy członkowie komitetu argumentowali za innym tokenem, na przykład (^...^), ale nie byli w stanie przekonać większości problemów za pomocą <...>.)

2

Jest to możliwe dzięki P99:

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

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

81

Jeśli nie można używać nawiasów i nie lubisz rozwiązanie SINGLE_ARG Mike'a, wystarczy zdefiniować przecinkami:

#define COMMA , 

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

Pomaga to również, jeśli chcesz stringify niektórych makr argumenty, 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óra drukuje std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

+9

#define COMMA wow, właśnie uratowałeś mi GODZINY pracy ... dlaczego nie pomyślałem o tych latach temu. Dziękujemy za udostępnienie tego pomysłu. Pozwala mi to nawet na tworzenie makr, których funkcje konfiguracyjne z różnymi argumentami się liczą. – moliad

+14

Plus 1 dla horroru – namezero

+0

nie działa, jeśli chcesz splafikować argument ... – kiw

12

Podobnie określenie FOO jak

#define UNPACK(...) __VA_ARGS__ 

#define FOO(type, name) UNPACK type name 

powołać się zawsze z nawiasami wokół argumentu typu, np

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

To może oczywiście być dobrym pomysłem do zilustrowania wywołania w komentarzu makro definicji.

+0

Nie wiem, dlaczego jest tak nisko, to znacznie lepsze rozwiązanie niż Mike Seymours. Jest szybki i prosty i całkowicie ukryty przed użytkownikiem. – iFreilicht

+1

@iFreilicht: Został opublikowany nieco ponad rok później. ;-) –

+1

A ponieważ również trudno jest zrozumieć, jak i dlaczego działa – VinGarcia