Reputation: 1659
I'm pretty sure the answer to this question is a "no" but I would be very glad to be proven wrong
Say I have a variadic template function
template <typename... Args>
void log(Args&&... args) { ... }
...and I also have macros to generate concrete logging functions. Like:
#define LOG_2(fname, arg0_type, arg0_name, arg1_type, arg1_name) \
void fname(arg0_type arg0_name, arg1_type arg1_name) { \
log(std::forward<arg0_type>(arg0_name), std::forward<arg1_type>(arg1_name)); }
Macros named LOG_0
to LOG_10
with similar code also exist.
I use them to generate special logging functions which serve two purposes. First they give strong types to the arguments, and second they give them names, which with code-completion cam be used as hints. Example:
LOG_2(progress_log, const char*, msg, float, part)
...
progress_log("Loading: ", complete/total);
Then besides the strong typing of the args, I get the following nice code completion hint:
So, is it possible to drop the explicit arity in the macros and write something which has the exact same effect but uses variadic macros? ...and also isn't macro which uses a counter to call one of the underlying arity-macros by concatenating the appropriate number with LOG_
.
I am aware I can count the arguments but most code-completion tools utterly fail to expand counting macros, especially if the possible argument count is big (like 10). Thus the code-completion effect gets lost. And if I would be willing to drop the code-completion hint effect, then I'd rather go with a something like LOG_FUN(progress_log, void(const char*, float))
which can also be used to generate compilation errors, but can have no argument names as hints
Upvotes: 2
Views: 952
Reputation: 72431
The Boost.Preprocessor library is made for just this sort of case when you really want to do something fancy with preprocessor macros. It can get a bit ugly, though.
#include <boost/preprocessor/variadic/to_seq.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/punctuation.hpp>
#include <boost/preprocessor/logical.hpp>
#include <utility>
template <typename... Args>
void log(Args&&...) {}
#define DEF_LOG_COMMA_BEFORE_PAIR(i) \
BOOST_PP_COMMA_IF(BOOST_PP_AND(i, BOOST_PP_NOT(BOOST_PP_MOD(i,2))))
#define DEF_LOG_EXPAND_PARAM(r, data, i, elem) \
DEF_LOG_COMMA_BEFORE_PAIR(i) elem
#define DEF_LOG_EXPAND_CALL(r, data, i, elem) \
DEF_LOG_COMMA_BEFORE_PAIR(i) \
BOOST_PP_IF( BOOST_PP_MOD(i,2), \
(elem), \
std::forward<elem> )
#define DEF_LOG_FUNC(name, ...) \
inline void name( \
BOOST_PP_SEQ_FOR_EACH_I(DEF_LOG_EXPAND_PARAM, ~, \
BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
) { log( \
BOOST_PP_SEQ_FOR_EACH_I(DEF_LOG_EXPAND_CALL, ~, \
BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
); }
DEF_LOG_FUNC(progress_log, const char*, msg, float, part)
int main() {
progress_log("Progress", 0.25);
}
To pull this apart, first look at the DEF_LOG_COMMA_BEFORE_PAIR
macro. It's just a helper that expands to a comma if the argument i
is an even number other than zero, and expands to nothing otherwise. By putting it before the result of transforming one vararg parameter, we get a comma every other item. (After might seem more natural, but it would be a little more complicated to exclude a comma after the last term than this way of excluding the comma before the first item.) Note BOOST_PP_COMMA_IF
is used to avoid expanding to a comma too early, which would confuse the other Boost macros.
Jumping down to the definition of the main macro DEF_LOG_FUNC
, you'll see it uses BOOST_PP_SEQ_FOR_EACH_I
twice. This is a tool which applies a supplied macro to each term from a passed sequence of arguments. The macro given must accept four arguments:
r
: used when you need multiple levels of preprocessor loops
data
: a sequence of tokens passed directly through BOOST_PP_SEQ_FOR_EACH_I
to each invocation of the macro
i
: the zero-based index of the sequence element
elem
: the actual sequence element
Neither of our macros actually needs data
, so we just pass ~
as a dummy token to both BOOST_PP_SEQ_FOR_EACH_I
uses. The two helper macros we pass to BOOST_PP_SEQ_FOR_EACH_I
are DEF_LOG_EXPAND_PARAM
for building the function parameter list and DEF_LOG_EXPAND_CALL
for building the argument list to the actual log
function. DEF_LOG_EXPAND_PARAM
doesn't need to do anything but add commas, using that DEF_LOG_COMMA_BEFORE_PAIR
helper. DEF_LOG_EXPAND_CALL
needs to do different things to even and odd arguments, so in addition to the DEF_LOG_COMMA_BEFORE_PAIR
tool again, it uses BOOST_PP_IF
to transform even (type) arguments to std::forward<elem>
and odd (parameter) arguments to (elem)
.
So as desired, the preprocessor replaces
DEF_LOG_FUNC(progress_log, const char*, msg, float, part)
as
inline void progress_log(
const char* msg , float part
) { log(
std::forward<const char*> (msg) , std::forward<float> (part)
); }
Upvotes: 1
Reputation: 37600
You can use some tags instead. If you need to provide hint for special cases then you can write some explicit specializations:
enum Category{normal, progress};
template<Category category = normal, typename... Args>
void log(Args... args) { ... }
template<>
void log<progress, char const *, float>(char const *, float) { ... }
// typing this will show generic and specialized variant hints in VS2017
log<progress>("Loading: ", complete/total);
Upvotes: 1