能否用可变参数宏生成带参数名提示的转发式日志函数?
Great question! I totally get why you’d want to ditch those numbered LOG_N macros while holding onto that critical parameter-name autocomplete—losing that would make the strongly-typed wrappers way less useful compared to just using a generic LOG_FUN. The good news is this is absolutely possible without resorting to parameter-counting hacks that break IDE hints.
Core Idea
We need a variadic macro that accepts a function name followed by alternating type-parameter pairs (e.g., const char*, msg, float, part), then expands into a strongly-typed function with explicit parameter names (for autocomplete) that forwards arguments to your variadic log function.
C++20 Implementation (Clean & Modern)
This uses __VA_OPT__ (a C++20 preprocessor feature) to handle recursive expansion cleanly:
#include <utility> // For std::forward // Helper to define a single parameter with forwarding reference #define LOG_PARAM(type, name) type&& name // Recursively expand all type-parameter pairs into function parameters #define LOG_EXPAND_PAIRS(...) \ __VA_OPT__(LOG_EXPAND_PAIRS_HELPER(__VA_ARGS__)) #define LOG_EXPAND_PAIRS_HELPER(type, name, ...) \ LOG_PARAM(type, name) \ __VA_OPT__(, LOG_EXPAND_PAIRS_HELPER(__VA_ARGS__)) // Recursively generate forward calls for each parameter #define LOG_FORWARD_ARGS(...) \ __VA_OPT__(LOG_FORWARD_ARGS_HELPER(__VA_ARGS__)) #define LOG_FORWARD_ARGS_HELPER(type, name, ...) \ std::forward<decltype(name)>(name) \ __VA_OPT__(, LOG_FORWARD_ARGS_HELPER(__VA_ARGS__)) // Main variadic LOG macro #define LOG(FUN_NAME, ...) \ void FUN_NAME(LOG_EXPAND_PAIRS(__VA_ARGS__)) { \ log(LOG_FORWARD_ARGS(__VA_ARGS__)); \ }
How It Works
When you write:
LOG(progress_log, const char*, msg, float, part)
The macro expands to:
void progress_log(const char*&& msg, float&& part) { log(std::forward<decltype(msg)>(msg), std::forward<decltype(part)>(part)); }
- Your IDE will see explicit parameter names (
msg,part) and provide full autocomplete. - Forwarding references preserve value categories (lvalues/rvalues) just like your original
logfunction.
C++17 & Earlier Compatibility
If you need to support older standards (no __VA_OPT__), we can use preprocessor counting and concatenation to handle fixed-but-arbitrary numbers of parameter pairs (adjust the count to match your original LOG_0 to LOG_10 needs):
#include <utility> // Helper macros for parameter expansion #define LOG_PARAM(type, name) type&& name #define LOG_FORWARD_ARG(type, name) std::forward<decltype(name)>(name) // Define expansions for up to 5 parameter pairs (matches LOG_10's 10 parameters) #define LOG_EXPAND_1(type1, name1) LOG_PARAM(type1, name1) #define LOG_EXPAND_2(type1, name1, type2, name2) LOG_EXPAND_1(type1, name1), LOG_PARAM(type2, name2) #define LOG_EXPAND_3(type1, name1, type2, name2, type3, name3) LOG_EXPAND_2(type1, name1, type2, name2), LOG_PARAM(type3, name3) #define LOG_EXPAND_4(type1, name1, type2, name2, type3, name3, type4, name4) LOG_EXPAND_3(...), LOG_PARAM(type4, name4) #define LOG_EXPAND_5(type1, name1, type2, name2, type3, name3, type4, name4, type5, name5) LOG_EXPAND_4(...), LOG_PARAM(type5, name5) #define LOG_FORWARD_1(type1, name1) LOG_FORWARD_ARG(type1, name1) #define LOG_FORWARD_2(type1, name1, type2, name2) LOG_FORWARD_1(...), LOG_FORWARD_ARG(type2, name2) #define LOG_FORWARD_3(type1, name1, type2, name2, type3, name3) LOG_FORWARD_2(...), LOG_FORWARD_ARG(type3, name3) #define LOG_FORWARD_4(type1, name1, ...) LOG_FORWARD_3(...), LOG_FORWARD_ARG(type4, name4) #define LOG_FORWARD_5(type1, name1, ...) LOG_FORWARD_4(...), LOG_FORWARD_ARG(type5, name5) // Count macro arguments to select the right expansion #define LOG_COUNT_ARGS(...) LOG_COUNT_ARGS_IMPL(__VA_ARGS__, 10,9,8,7,6,5,4,3,2,1,0) #define LOG_COUNT_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N #define LOG_SELECT_EXPAND(n) LOG_CONCAT(LOG_EXPAND_, n/2) #define LOG_SELECT_FORWARD(n) LOG_CONCAT(LOG_FORWARD_, n/2) #define LOG_CONCAT(a,b) LOG_CONCAT_IMPL(a,b) #define LOG_CONCAT_IMPL(a,b) a##b // Main LOG macro #define LOG(FUN_NAME, ...) \ void FUN_NAME(LOG_SELECT_EXPAND(LOG_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)) { \ log(LOG_SELECT_FORWARD(LOG_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)); \ }
Key Notes
- This version pre-defines expansions for up to 5 pairs (10 parameters), matching your original
LOG_10limit. You can add more pairs if needed. - It still generates functions with explicit parameter names, so autocomplete works perfectly.
Why This Works for Autocomplete
Unlike hacks that concatenate LOG_ with a parameter count (which IDEs can’t parse for hints), this macro directly generates function signatures with concrete parameter names. Your IDE sees the expanded function just like any other hand-written function, so it can provide full parameter name and type hints.
内容的提问来源于stack exchange,提问作者Borislav Stanimirov

