Dấu phẩy trong macro C / C ++


103

Giả sử chúng ta có một macro như thế này

#define FOO(type,name) type name

Cái mà chúng tôi có thể sử dụng như

FOO(int, int_var);

Nhưng không phải lúc nào cũng đơn giản như vậy:

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

Tất nhiên chúng tôi có thể làm:

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

mà không phải là rất công thái học. Cộng với sự không tương thích kiểu phải được xử lý. Bất kỳ ý tưởng làm thế nào để giải quyết điều này với macro?


Tôi đoán bạn phải thoát khỏi các ký tự có nghĩa để biến chúng thành nghĩa đen.
Jite 12/12/12

Ít nhất trong C ++, bạn có thể đặt typedef ở bất cứ đâu, vì vậy tôi không chắc tại sao bạn nói nó phải là "trước".
Vaughn Cato

Câu trả lời:


108

Bởi vì dấu ngoặc nhọn cũng có thể đại diện (hoặc xảy ra trong) các toán tử so sánh <, >, <=>=, mở rộng vĩ mô không thể bỏ qua dấu phẩy bên trong dấu ngoặc nhọn như nó trong dấu ngoặc đơn. (Đây cũng là vấn đề đối với dấu ngoặc vuông và dấu ngoặc nhọn, mặc dù chúng thường xảy ra dưới dạng các cặp cân bằng.) Bạn có thể đặt đối số macro trong dấu ngoặc đơn:

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

Vấn đề là sau đó, tham số vẫn được đặt trong ngoặc đơn bên trong phần mở rộng macro, điều này ngăn nó được đọc như một kiểu trong hầu hết các ngữ cảnh.

Một mẹo hay để giải quyết vấn đề này là trong C ++, bạn có thể trích xuất một tên kiểu từ tên kiểu trong ngoặc đơn bằng cách sử dụng kiểu hàm:

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

Bởi vì việc tạo kiểu hàm bỏ qua dấu ngoặc đơn, bạn có thể sử dụng macro này có hoặc không có dấu ngoặc đơn trong đó tên kiểu không bao gồm dấu phẩy:

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

Tất nhiên, trong C, điều này không cần thiết vì tên kiểu không thể chứa dấu phẩy bên ngoài dấu ngoặc đơn. Vì vậy, đối với macro đa ngôn ngữ, bạn có thể viết:

#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

Điều này thật tuyệt. Nhưng làm thế nào bạn phát hiện ra điều này? Tôi đã thử rất nhiều thủ thuật và thậm chí chưa bao giờ nghĩ rằng một loại hàm sẽ khắc phục được sự cố.
Will Custode,

@WilliamCustode như tôi nhớ lại, tôi đã nghiên cứu ngữ pháp của các kiểu hàm và khai báo hàm có tham chiếu đến vấn đề phân tích cú pháp khó chịu nhất, vì vậy thật tình cờ khi tôi biết rằng dấu ngoặc đơn thừa có thể được áp dụng cho một kiểu trong ngữ cảnh đó.
ecatmur

Tôi đã tìm thấy vấn đề với phương pháp này khi làm việc với các mẫu. Giả sử mã tôi muốn là: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Nếu tôi áp dụng giải pháp này ở đây, các cấu trúc đằng sau macro trở thành các kiểu phụ thuộc và tiền tố tên kiểu bây giờ là bắt buộc đối với kiểu. Bạn có thể thêm nó, nhưng loại trừ đã bị hỏng, vì vậy bây giờ bạn phải liệt kê thủ công các đối số loại để gọi hàm. Tôi đã sử dụng phương pháp xác định macro cho dấu phẩy của Temple. Nó có thể không đẹp, nhưng nó hoạt động hoàn hảo.
Roger Sanders

Một vấn đề nhỏ về câu trả lời: Nó nói rằng dấu phẩy bị bỏ qua bên trong []{}không phải vậy, nó chỉ hoạt động với điều ()đáng buồn. Xem: Tuy nhiên, không có yêu cầu cho dấu ngoặc vuông hoặc niềng răng để cân bằng ...
VinGarcia

Rất tiếc, điều này không hoạt động trong MSVC: godbolt.org/z/WPjYW8 . Có vẻ như MSVC không cho phép thêm nhiều parens và không phân tích được nó. Một giải pháp mà không phải là thanh lịch nhưng nhanh hơn (mẫu instantiations ít hơn) là để bọc các lập luận bằng dấu phẩy ed vào một macro wrapper: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Việc chuyển các đối số hiện có thể dễ dàng ngay cả khi thông qua nhiều macro và đối với các loại đơn giản, bạn có thể bỏ qua PROTECT. Tuy nhiên, các loại hàm trở thành con trỏ hàm khi được đánh giá như thế này
Flamefire

119

Nếu bạn không thể sử dụng dấu ngoặc đơn và bạn không thích giải pháp SINGLE_ARG của Mike, chỉ cần xác định COMMA:

#define COMMA ,

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

Điều này cũng hữu ích nếu bạn muốn xâu chuỗi một số đối số macro, như trong

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

bản in nào std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".


16
#define COMMA wow, bạn vừa tiết kiệm cho tôi GIỜ làm việc ... tại sao tôi không nghĩ đến điều này nhiều năm trước. Cảm ơn vì đã chia sẻ ý kiến ​​này. Điều này thậm chí còn cho phép tôi xây dựng các macro có chức năng thiết lập với các đối số khác nhau hoàn toàn được tính.
moliad

28
Thêm 1 cho sự kinh dị
namezero

1
@kiw Nếu bạn #define STRVX(...) STRV(__VA_ARGS__)#define STRV(...) # __VA_ARGS__, sau đó std::cout << STRV(type<A COMMA B>) << std::endl;sẽ in type<A COMMA B>std::cout << STRVX(type<A COMMA B>) << std::endl;sẽ in type<A , B>. ( STRVlà cho "variadic stringify" và STRVXdành cho "variadic stringify mở rộng".)
not-a-user

1
@ not-a-user vâng, nhưng với macro đa dạng, bạn không cần COMMAmacro ngay từ đầu. Đó là những gì tôi đã kết thúc.
kiw

Tôi sẽ không bao giờ sử dụng điều đó, nhưng +1 vì vui nhộn.
Rafael Baptista

58

Nếu bộ tiền xử lý của bạn hỗ trợ macro đa dạng:

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

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

Nếu không, nó sẽ tẻ nhạt hơn một chút:

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

Ôi, trời ơi ... Tại sao? Tại sao không chỉ đặt trong dấu ngoặc đơn?

15
@VladLazarenko: Vì không phải lúc nào bạn cũng có thể đặt các đoạn mã tùy ý trong dấu ngoặc đơn. Đặc biệt, bạn không thể đặt dấu ngoặc đơn xung quanh tên kiểu trong một bộ khai báo, đó chính là điều mà đối số này trở thành.
Mike Seymour

2
... và cũng có thể bởi vì bạn chỉ có thể sửa đổi định nghĩa macro chứ không phải tất cả các vị trí gọi nó (có thể không nằm trong tầm kiểm soát của bạn hoặc có thể trải rộng trên hàng nghìn tệp, v.v.). Điều này xảy ra, ví dụ, khi thêm macro để đảm nhận các nhiệm vụ từ một hàm giống tên.
BeeOnRope

32

Chỉ cần xác định FOO

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Sau đó, gọi nó luôn với dấu ngoặc đơn xung quanh đối số kiểu, ví dụ:

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

Tất nhiên có thể là một ý tưởng hay để minh họa các lời gọi trong một nhận xét về định nghĩa macro.


Không chắc tại sao điều này lại giảm đi rất nhiều, đó là một giải pháp tốt hơn nhiều so với Mike Seymours. Nó nhanh chóng và đơn giản và hoàn toàn ẩn với người dùng.
iFreilicht

3
@iFreilicht: Nó đã được đăng hơn một năm sau đó. ;-)
Chúc mừng và hth. - Alf

5
Và bởi vì thật khó để hiểu cách thức và lý do tại sao nó hoạt động
VinGarcia

@VinGarcia, bạn có thể giải thích tại sao / cách nó hoạt động? Tại sao cần phải có dấu ngoặc đơn khi gọi nó? Làm gì UNPACKkhi sử dụng như thế này ) UNPACK type name? Tại sao lại chọn typeđúng loại khi sử dụng ) UNPACK type name? Cái quái gì đang xảy ra ở đây vậy?
người dùng

Không có @user, có lẽ Cheers and hth có thể trả lời bạn
VinGarcia

4

Có ít nhất hai cách để làm điều này. Đầu tiên, bạn có thể xác định một macro có nhiều đối số:

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

nếu bạn làm điều đó, bạn có thể thấy rằng bạn sẽ xác định nhiều macro hơn để xử lý nhiều đối số hơn.

Thứ hai, bạn có thể đặt dấu ngoặc đơn xung quanh đối số:

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

nếu bạn làm điều đó, bạn có thể thấy rằng các dấu ngoặc đơn bổ sung làm sai cú pháp của kết quả.


Đối với giải pháp đầu tiên, mỗi macro sẽ phải có một tên khác nhau, vì macro không quá tải. Và điều thứ hai, nếu bạn đang chuyển một tên kiểu, rất có thể nó sẽ được sử dụng để khai báo một biến (hoặc một typedef), vì vậy dấu ngoặc đơn sẽ gây ra vấn đề.
James Kanze 12/12/12

4

Điều này có thể thực hiện được với P99 :

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

Đoạn mã trên chỉ xóa dấu phẩy cuối cùng trong danh sách đối số một cách hiệu quả. Kiểm tra với clang -E(P99 yêu cầu trình biên dịch C99).


3

Câu trả lời đơn giản là bạn không thể. Đây là một tác dụng phụ của việc lựa chọn <...>đối số mẫu; các <>cũng xuất hiện trong bối cảnh không cân bằng nên cơ chế vĩ mô không thể được mở rộng để xử lý chúng như nó xử lý dấu ngoặc đơn. (Chẳng hạn, một số thành viên ủy ban đã tranh luận về một mã thông báo khác (^...^), nhưng họ không thể thuyết phục phần lớn các vấn đề khi sử dụng <...>.)


2
(^...^)đây là một khuôn mặt hạnh phúc :)
CygnusX1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.