Koma dalam makro C / C ++

103

Katakanlah kita memiliki makro seperti ini

#define FOO(type,name) type name

Yang bisa kita gunakan seperti

FOO(int, int_var);

Tetapi tidak selalu sesederhana itu:

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

Tentu saja kami bisa melakukan:

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

yang tidak terlalu ergonomis. Ketidaksesuaian tipe Plus harus ditangani. Ada ide bagaimana mengatasi ini dengan makro?

PoP
sumber
Saya menduga Anda harus melarikan diri dari karakter dengan makna untuk menjadikannya literal.
Jite
Setidaknya di C ++, Anda dapat meletakkan typedef di mana saja, jadi saya tidak yakin mengapa Anda mengatakannya harus "terlebih dahulu".
Vaughn Cato

Jawaban:

108

Karena kurung sudut juga dapat mewakili (atau terjadi pada) operator perbandingan <, >, <=dan >=, ekspansi makro tidak bisa mengabaikan koma di dalam tanda kurung sudut seperti itu tidak dalam tanda kurung. (Ini juga masalah untuk tanda kurung siku dan tanda kurung siku, meskipun itu biasanya terjadi sebagai pasangan seimbang.) Anda bisa mengapit argumen makro dalam tanda kurung:

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

Masalahnya adalah bahwa parameter tetap dalam tanda kurung di dalam perluasan makro, yang mencegahnya dibaca sebagai tipe di sebagian besar konteks.

Trik yang bagus untuk mengatasinya adalah bahwa di C ++, Anda dapat mengekstrak nama jenis dari nama jenis dalam tanda kurung menggunakan jenis fungsi:

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

Karena tipe fungsi pembentuk mengabaikan tanda kurung ekstra, Anda bisa menggunakan makro ini dengan atau tanpa tanda kurung di mana nama tipe tidak menyertakan koma:

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

Di C, tentu saja, ini tidak diperlukan karena nama tipe tidak boleh berisi koma di luar tanda kurung. Jadi, untuk makro lintas bahasa Anda bisa menulis:

#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.dll
sumber
Ini luar biasa. Tetapi bagaimana Anda mengetahui tentang ini? Saya telah mencoba banyak trik dan bahkan tidak pernah berpikir bahwa jenis fungsi akan memperbaiki masalah tersebut.
Akan Custode
@WilliamCustode seingat saya, saya telah mempelajari tata bahasa dari jenis fungsi dan deklarasi fungsi dengan mengacu pada masalah penguraian yang paling menjengkelkan, jadi kebetulan saya menyadari bahwa tanda kurung yang berlebihan dapat diterapkan ke jenis dalam konteks tersebut.
ecatmur
Saya menemukan masalah dengan metode ini saat bekerja dengan template. Katakanlah kode yang saya inginkan adalah ini: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Jika saya menerapkan solusi ini di sini, struct di belakang makro menjadi tipe dependen, dan awalan nama tipe sekarang diperlukan pada tipe. Anda dapat menambahkannya, tetapi pengurangan jenis telah rusak, jadi Anda sekarang harus membuat daftar argumen jenis secara manual untuk memanggil fungsi tersebut. Saya akhirnya menggunakan metode temple untuk mendefinisikan makro untuk koma. Ini mungkin tidak terlihat cantik, tetapi berfungsi dengan sempurna.
Roger Sanders
Sebuah masalah kecil pada jawaban: Ini menyatakan bahwa koma diabaikan dalam [] dan {}, mereka tidak, hanya bekerja dengan ()sedih. Lihat: Namun, tidak ada persyaratan untuk tanda kurung siku atau kawat gigi untuk menyeimbangkan ...
VinGarcia
Sayangnya ini tidak berfungsi di MSVC: godbolt.org/z/WPjYW8 . Tampaknya MSVC tidak mengizinkan penambahan banyak tanda kurung dan gagal untuk menguraikannya. Sebuah solusi yang tidak elegan tapi lebih cepat (template instantiations kurang) adalah untuk membungkus argumen koma-ed menjadi makro wrapper: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Meneruskan argumen sekarang dengan mudah dapat dilakukan bahkan melalui beberapa makro dan untuk tipe sederhana Anda dapat mengabaikan PROTECT. Namun jenis fungsi menjadi penunjuk fungsi ketika dievaluasi seperti ini
Flamefire
119

Jika Anda tidak dapat menggunakan tanda kurung dan tidak menyukai solusi SINGLE_ARG Mike, cukup tentukan COMMA:

#define COMMA ,

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

Ini juga membantu jika Anda ingin merangkai beberapa argumen makro, seperti dalam

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

yang mencetak std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

bukan-pengguna
sumber
16
#define COMMA wow, Anda baru saja menyelamatkan saya JAM kerja ... kenapa saya tidak memikirkan ini bertahun-tahun yang lalu. Terima kasih telah membagikan ide ini. Ini bahkan memungkinkan saya untuk membuat makro yang mengatur fungsi dengan jumlah argumen yang berbeda secara bersamaan.
moliad
28
Ditambah 1 untuk horor
namezero
1
@kiw Jika Anda #define STRVX(...) STRV(__VA_ARGS__)dan #define STRV(...) # __VA_ARGS__, maka std::cout << STRV(type<A COMMA B>) << std::endl;akan mencetak type<A COMMA B>dan std::cout << STRVX(type<A COMMA B>) << std::endl;akan mencetak type<A , B>. ( STRVuntuk "variadic stringify", dan STRVXuntuk "extended variadic stringify".)
bukan-pengguna
1
@ not-a-user ya, tetapi dengan makro variadic Anda tidak memerlukan COMMAmakro sejak awal. Itulah yang akhirnya saya dapatkan.
kiw
Saya tidak pernah menggunakan itu, tetapi +1 karena lucu.
Rafael Baptista
58

Jika preprocessor Anda mendukung makro variadic:

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

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

Jika tidak, ini sedikit lebih membosankan:

#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
sumber
Oh, astaga ... Kenapa? Mengapa tidak menyertakan tanda kurung saja?
15
@VladLazarenko: Karena Anda tidak selalu dapat meletakkan potongan kode arbitrer dalam tanda kurung. Secara khusus, Anda tidak bisa meletakkan tanda kurung di sekitar nama tipe di deklarator, yang menjadi argumen ini.
Mike Seymour
2
... dan juga karena Anda mungkin hanya dapat mengubah definisi makro dan tidak semua tempat yang memanggilnya (yang mungkin tidak di bawah kendali Anda, atau mungkin tersebar di 1000-an file, dll). Ini terjadi, misalnya, saat menambahkan makro untuk mengambil alih tugas dari fungsi bernama serupa.
BeeOnRope
32

Definisikan FOOsebagai

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Kemudian panggil selalu dengan tanda kurung di sekitar argumen tipe, misalnya

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

Tentu saja merupakan ide yang bagus untuk memberikan contoh pemanggilan dalam komentar tentang definisi makro.

Cheers and hth. - Alf
sumber
Tidak yakin mengapa ini sangat jauh, ini adalah solusi yang jauh lebih baik daripada Mike Seymours. Ini cepat dan sederhana dan sepenuhnya tersembunyi dari pengguna.
iFreilicht
3
@iFreilicht: Itu diposting lebih dari setahun kemudian. ;-)
Bersulang dan hth. - Alf
5
Dan karena juga sulit untuk memahami bagaimana dan mengapa ini bekerja
VinGarcia
@VinGarcia, Anda dapat menjelaskan mengapa / bagaimana cara kerjanya? Mengapa tanda kurung diperlukan saat memanggilnya? Apa yang UNPACKdilakukan jika digunakan seperti ini ) UNPACK type name? Mengapa typemendapatkan tipe dengan benar saat digunakan ) UNPACK type name? Sebenarnya apa yang terjadi disini?
pengguna
Tidak @user, mungkin Cheers dan hth bisa menjawab Anda
VinGarcia
4

Setidaknya ada dua cara untuk melakukan ini. Pertama, Anda bisa menentukan makro yang membutuhkan banyak argumen:

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

jika Anda melakukannya, Anda mungkin mendapati bahwa Anda akhirnya menentukan lebih banyak makro untuk menangani lebih banyak argumen.

Kedua, Anda dapat meletakkan tanda kurung di sekitar argumen:

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

jika Anda melakukannya, Anda mungkin menemukan bahwa tanda kurung tambahan mengacaukan sintaks hasil.

Pete Becker
sumber
Untuk solusi pertama, setiap makro harus memiliki nama yang berbeda, karena makro tidak membebani secara berlebihan. Dan yang kedua, jika Anda mengirimkan nama tipe, ada kemungkinan besar nama itu akan digunakan untuk mendeklarasikan variabel (atau typedef), jadi tanda kurung akan menimbulkan masalah.
James Kanze
4

Ini dimungkinkan dengan P99 :

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

Kode di atas secara efektif hanya menghapus koma terakhir dalam daftar argumen. Periksa dengan clang -E(P99 membutuhkan kompiler C99).

xiaq
sumber
3

Jawaban sederhananya adalah Anda tidak bisa. Ini adalah efek samping dari pilihan <...>argumen template; yang <dan >juga muncul dalam konteks yang tidak seimbang sehingga mekanisme makro tidak dapat diperpanjang untuk menangani mereka seperti itu menangani tanda kurung. (Beberapa anggota komite telah memperdebatkan token yang berbeda, katakanlah (^...^), tetapi mereka tidak dapat meyakinkan sebagian besar masalah menggunakan <...>.)

James Kanze
sumber
2
(^...^)ini adalah satu wajah bahagia :)
CygnusX1