1
votes

Problem:

I'm looking to write a variadic macro

#define WRAP(token, ...) 

which, when called with a token and N arguments

WRAP(decltype, a, b, c)

will expand into a comma-separated list of the arguments wrapped in the token

decltype(a), decltype(b), decltype(c)

This would allow me to write something like the following:

#define MACRO(...) \
    Class< WRAP(decltype, __VA_ARGS__) >::Function();

If I call it as follows:

MACRO(a, b, c)

it would result in:

Class<decltype(a), decltype(b), decltype(c)>::Function(0;

I have no idea how to achieve this. Is it possible? Perhaps with BOOST_PP or some such?

Motivation:

I have a macro for logging:

#define LOG(fmt, ...) \
    logger::instance().push(__FUNCTION__, fmt, __VA_ARGS__);

I have a variadic class template which is able to verify the arguments match the provided format string

template<typename... Ts>
struct Format
{
    template<std::size_t N>
    static constexpr bool check(const char (&fmt)[N], std::size_t n);
};

If I define a macro with a fixed number of arguments I can call my format checking function:

#define LOG(fmt, a) \
    static_assert(Format<decltype(a1)>::check(fmt, 0), ""); \
    logger::instance().push(__FUNCTION__, fmt, a);

However, if I use a variadic macro, this won't work for more than 1 argument:

#define LOG(fmt, ...) \
    static_assert(Format<decltype(__VA_ARGS__)>::check(fmt, 0), ""); \
    logger::instance().push(__FUNCTION__, fmt, __VA_ARGS__);

That's because decltype(__VA_ARGS__) is obviously invalid syntax.

to fix it, I need to expand __VA_ARGS__ into decltype(a1), decltype(a2), decltype(a3)

Variadic function template?

I have tried to achieve this using a constexpr variadic function template, but I am unable to pass fmt to a static_assert as it is no longer a string literal at this point:

template<size_t N, typename... Ts>
constexpr void check(const char (&fmt)[N], const Ts&...)
{
    static_assert(Format<Ts...>::check(fmt, 0), "");
}

Trying to call this check function

check("%s", "hello world"); 

fails to compile:

main.cpp:216:46:   in constexpr expansion of ‘Format<T, Ts ...>::check<7ul>((* & fmt), 0ul)’
main.cpp:216:5: error: ‘fmt’ is not a constant expression
1
Why not use a variadic function template instead? It preserves type information and you can use the deduced parameter pack to decide the type of Format<>. - Pradhan
Why is a function with variadic template argument not enough here? template <class... Types> void log(Types... args) { return Class<Types...>(args...); } - Thomas Moulard
Further, if I pass fmt to a function, such that it's type is const char (&fmt)[N], I cannot use that in a static_assert: The following fails to compile: template<size_t N, typename... Ts> constexpr void check(const char (&fmt)[N], const Ts&...) { static_assert(Format<Ts...>::check(fmt, 0), ""); } with error ‘fmt’ is not a constant expression - Steve Lorimer

1 Answers

2
votes

It's very simple with Boost.PP:

#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>

#define COMMA_SEP(r, token, i, e) BOOST_PP_COMMA_IF(i) token(e)
#define WRAP(token, ...) BOOST_PP_SEQ_FOR_EACH_I(COMMA_SEP, token, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

Edit: The above won't work if __VA_ARGS__ is empty, to solve that, please refer to this solution, but note that the solution won't work for MSVC because their preprocessor is non-standard-conformant, if you want to support MSVC, just use BOOST_PP_IS_EMPTY in place of ISEMPTY.