2015-08-17 14 views
7

Tôi đang sử dụng một số macro ghi nhật ký, có nghĩa vụ in ra thông tin được cung cấp bởi macro __PRETTY_FUNCTION__ và nếu tên và giá trị cần thiết tối đa hai đối số. Một phiên bản đơn giản hóa của mã của tôi trông giống nhưMacro biến thể không có đối số

template<typename Value1, typename Value2> 
void Log(std::string const& function, 
     std::string const& variable_1 = "", Value1 value_1 = Value1(0), 
     std::string const& variable_2 = "", Value2 value_2 = Value2(0)) { 
    std::cout << function << " " 
       << variable_1 << " " << value_1 << " " 
       << variable_2 << " " << value_2 << std::endl; 
} 
#define LOG0() Log(__PRETTY_FUNCTION__) 
#define VARIABLE(value) #value, value 
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value)) 
#define LOG2(value, value1) Log(__PRETTY_FUNCTION__, VARIABLE(value), VARIABLE(value1)) 
#define LOG(arg0, arg1, arg2, arg, ...) arg 
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0) 
#define Debug(...) CHOOSE(__VA_ARGS__)(__VA_ARGS__) 

tôi có thể sử dụng các macro như

Debug(); 
int x = 0; 
Debug(x); 
int y = 1; 
Debug(x, y); 

Khi tôi biên dịch mã này với kêu vang tôi nhận được một kết quả đẹp có chứa lớp và thông tin chức năng cũng như tên và giá trị của các biến. Nhưng tôi cũng nhận được cảnh báo rằng mã tuân thủ chuẩn không được phép có đối số zerodic.

warning: token pasting of ',' and __VA_ARGS__ is a GNU extension [-Wgnu-zero-variadic-macro-arguments] 
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0) 
         ^
warning: must specify at least one argument for '...' parameter of variadic macro [-Wgnu-zero-variadic-macro-arguments] 
Debug();  

Gcc mặt khác thất bại trong việc biên dịch với

error: expected primary-expression before ‘)’ token 
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value)) 
                  ^
Debug(); 

Rõ ràng nó là nguy hiểm để làm việc với zero luận variadic.

  1. Có cách nào để tôi có thể biến mã này thành mã tuân thủ chuẩn mà không loại bỏ sự tiện lợi khi chỉ có một macro mất 0 đến 2 đối số không?
  2. Nếu điều này là không thể, có cách nào để làm cho gcc cũng biên dịch mã này?
+0

Hành vi của ## __ VA_ARGS__ phụ thuộc vào phiên bản CPP của bạn. Nó có thể đơn giản như thêm khoảng trống trước dấu phẩy trướC##: 'LOG (, ## __ VA_ARGS __, ...' – Pianosaurus

+0

Ngoài ra, các macro của bạn hoạt động như với tôi trong g ++ 4.9.2, ngoại trừ g ++ không thể suy ra loại mẫu của các giá trị khi chúng không được đưa ra. Bạn đã đăng một ví dụ hoàn chỉnh, hoặc là một cái gì đó bị thiếu? – Pianosaurus

Trả lời

6

Phần cứng này phân biệt giữa Debug()Debug(x). Trong cả hai trường hợp, bạn về mặt kỹ thuật truyền một đối số cho macro Debug. Trong trường hợp đầu tiên, chuỗi mã thông báo của đối số đó trống và trong trường hợp thứ hai, nó chứa một mã thông báo duy nhất. Những trường hợp này có thể được phân biệt với a trick due to to Jens Gustedt.

Đây là lừa:

#define COMMA_IF_PARENS(...) , 

Quan sát rằng COMMA_IF_PARENS X tạo ra một dấu phẩy nếu X bắt đầu với (...), và nếu không mở rộng đến một chuỗi dấu hiệu không chứa thêm dấu phẩy (top-level). Tương tự, COMMA_IF_PARENS X() tạo dấu phẩy nếu X trống hoặc bắt đầu bằng (...) và nếu không mở rộng thành chuỗi ký tự không chứa dấu phẩy (cấp cao nhất) bổ sung. (Trong mỗi trường hợp, trình tự thẻ cũng chứa tất cả các dấu phẩy cấp cao nhất từ ​​X chính nó.)

Chúng tôi có thể sử dụng thủ thuật như thế này:

#define CHOOSE(...) \ 
    LOG(__VA_ARGS__ \ 
     COMMA_IF_PARENS __VA_ARGS__ \ 
     COMMA_IF_PARENS __VA_ARGS__(), \ 
     CHOICES) 

Lưu ý rằng:

  • COMMA_IF_PARENS __VA_ARGS__ tạo số lượng dấu phẩy trong __VA_ARGS__ cộng 1 nếu __VA_ARGS__ bắt đầu bằng (...).
  • COMMA_IF_PARENS __VA_ARGS__() tạo số lượng dấu phẩy trong __VA_ARGS__ cộng 1 nếu __VA_ARGS__ trống hoặc bắt đầu bằng (...). (Lưu ý rằng điều này có thể thất bại nếu __VA_ARGS__ kết thúc bằng tên của macro giống như chức năng và chúng tôi không giải quyết vấn đề tiềm ẩn ở đây.)

Hãy c là số dấu phẩy trong __VA_ARGS__, p là 1 nếu __VA_ARGS__ bắt đầu với (...) và 0 nếu ngược lại, và e là 1 nếu __VA_ARGS__ trống và 0 nếu ngược lại.

Số đối số vĩ mô được sản xuất trước khi CHOICES là 3 c + 2 p + e. Lấy modulo 3, số lượng dấu phẩy là 0 hoặc 2 cho một đối số bình thường, và 1 nếu chúng ta có một danh sách các đối số rỗng.

này cho chúng ta 6 trường hợp chúng ta quan tâm:

#define CHOICES LOG2, impossible, LOG2, LOG1, LOG0, LOG1 
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg 

Tuy nhiên, điều này không hoàn toàn làm việc, bởi vì chúng ta cần phải trì hoãn việc mở rộng gọi vĩ mô LOG(...) cho đến sau khi chúng tôi mở rộng bộ máy COMMA_IF_PARENS. Một cách để làm điều đó là:

#define LPAREN (
#define EXPAND(...) __VA_ARGS__ 
#define CHOOSE(...) EXPAND(LOG LPAREN COMMA_IF_PARENS [...])) 

Chúng ta cũng nên thêm dấu phẩy khác đến hết CHOICES để chúng ta luôn luôn có một (có thể rỗng) Lập luận tương ứng với tham số của ...LOG.

Đưa nó tất cả cùng nhau, chúng ta có được điều này:

#define COMMA_IF_PARENS(...) , 
#define LPAREN (
#define EXPAND(...) __VA_ARGS__ 
#define CHOOSE(...) \ 
    EXPAND(LOG LPAREN \ 
     __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__(), \ 
     LOG2, impossible, LOG2, LOG1, LOG0, LOG1,)) 
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg 

với mọi thứ khác không thay đổi từ mã của bạn. (Điều này có thể được khái quát hóa nhiều hơn, nhưng ở trên là đủ để chứng minh kỹ thuật.)

Các vấn đề liên quan