[preprocessor] BOOST_PP_VARIADIC_SIZE() and g++ in 1.52

I'm trying to use a variadic macro to solve a problem. We use a third-party function I can't modify -- actually a family of functions of arbitrary arity. Before each call to that function, I want to examine and possibly modify its first argument. (So not completely arbitrary arity: there's always at least one argument.) What I'd really like is a variadic function. Unfortunately the compilers we use don't yet support those. We do have cross-platform support for variadic macros, albeit with idiosyncratic syntax. My problem is that I need: intercept(first, second, third) to expand to: targetfunc(transform(first), second, third) but I need: intercept(first) to expand to: targetfunc(transform(first)) // <= no trailing comma This is the role of BOOST_PP_COMMA_IF(). In Visual Studio 2010 I can write BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)), and the code behaves as expected. In g++ 4.1, 4.2 and 4.4, however, my first problem is that BOOST_PP_VARIADICS is not set by default. When I set it explicitly, as permitted by the documentation[0], a call such as intercept("first", "second") (comma required) succeeds, but a call such as intercept("first") (comma forbidden) fails. Using -E and examining the preprocessed output from intercept("first") makes clear that BOOST_PP_COMMA_IF() is expanding to a comma, hence BOOST_PP_VARIADIC_SIZE() is expanding to nonzero. In fact, when I change the definition of the intercept() macro below to: #define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS))) I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second"). Is it possible to achieve what I want with my present suite of compilers? Is there some workaround we could use until (at some point in the indefinite future) we've upgraded all our compilers so that even the oldest of them supports variadic functions? [0] http://www.boost.org/doc/libs/1_52_0/libs/preprocessor/doc/topics.html ================ source code follows ================ #include <iostream> #include <string> #include <boost/preprocessor/variadic/size.hpp> #include <boost/preprocessor/punctuation/comma_if.hpp> // no targetfunc(void) void targetfunc(const std::string& first) { std::cout << "targetfunc('" << first << "')\n"; } void targetfunc(const std::string& first, const std::string& second) { std::cout << "targetfunc('" << first << "', '" << second << "')\n"; } // ... assume a family of targetfunc() arities ... // want to implicitly "do something" to targetfunc()'s first arg std::string transform(const std::string& arg) { return "transformed " + arg; } #if defined(_MSC_VER) // anonymous variadic arguments and __VA_ARGS__ keyword #define intercept(FIRST, ...) \ targetfunc(transform(FIRST) \ BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) \ __VA_ARGS__) #else // collectively-named variadic arguments #define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE(ARGS)) \ ARGS) #endif int main(int argc, char *argv[]) { std::cout << "BOOST_PP_VARIADICS = " << BOOST_PP_VARIADICS << "\n"; intercept("first", "second"); intercept("first"); // <= this line has trouble with g++ 4.1, 4.2, 4.4 return 0; }

In fact, when I change the definition of the intercept() macro below to:
#define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS)))
I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second").
As I understand it, it is impossible to reliably detect an empty argument list in all cases, so BOOST_PP_VARIADIC_SIZE() returns 1 for an empty argument list. Take a look at this thread [1] for an explanation and a workaround. Regards, Nate [1] http://boost.2283326.n4.nabble.com/Alternative-implementation-for-BOOST-PP-V...

On Tue, Dec 11, 2012 at 11:16 PM, Nathan Ridge <zeratul976@hotmail.com> wrote:
In fact, when I change the definition of the intercept() macro below to:
#define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS)))
I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second").
As I understand it, it is impossible to reliably detect an empty argument list in all cases, so BOOST_PP_VARIADIC_SIZE() returns 1 for an empty argument list. Take a look at this thread [1] for an explanation and a workaround.
Hmm, am I trying to solve the wrong problem? The briefest explanation of what I want is to take a macro argument list with zero or more arguments and construct a function argument list with one more (initial) argument than that. I just need MACRO() to expand to function(first), but MACRO(anything) to expand to function(first, anything). Is there some more reliable way than BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE()) to make the comma appear only when required?

In fact, when I change the definition of the intercept() macro below to:
#define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS)))
I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second").
As I understand it, it is impossible to reliably detect an empty argument list in all cases, so BOOST_PP_VARIADIC_SIZE() returns 1 for an empty argument list. Take a look at this thread [1] for an explanation and a workaround.
Hmm, am I trying to solve the wrong problem?
The briefest explanation of what I want is to take a macro argument list with zero or more arguments and construct a function argument list with one more (initial) argument than that.
I just need MACRO() to expand to function(first), but MACRO(anything) to expand to function(first, anything).
Is there some more reliable way than BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE()) to make the comma appear only when required?
The preprocessor only really understand commas and parentheses. So, as far as it's concerned, the definition of "the size of a variadic argument list" is "the number of (top-level) commas in the list plus one". You want to make an additional distinction - in the case where there are no top-level commas, you want to distinguish between the case where there are some tokens present, and the case where there are none. As Paul Mensonides explains in the thread I linked to, this is not possible in the general case. It *is* possible to write a version of BOOST_PP_VARIADIC_SIZE() that works the way you want in many cases, quite possibly including your use cases. I believe Boost.Preprocessor does not provide such a version on principle (because it would not work in all cases). However, nothing stops you from using your own version that does this. Gennadiy Rozental posted such a version in the first post of the thread I linked to. Regards, Nate

On Wed, Dec 12, 2012 at 11:59 AM, Nathan Ridge <zeratul976@hotmail.com> wrote:
It *is* possible to write a version of BOOST_PP_VARIADIC_SIZE() that works the way you want in many cases, quite possibly including your use cases. I believe Boost.Preprocessor does not provide such a version on principle (because it would not work in all cases). However, nothing stops you from using your own version that does this. Gennadiy Rozental posted such a version in the first post of the thread I linked to.
Okay, thank you Nathan, and thank you also Gennadiy. Your version does appear to address my use case.

On 12/12/2012 11:59 AM, Nathan Ridge wrote:
In fact, when I change the definition of the intercept() macro below to:
#define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS)))
I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second").
As I understand it, it is impossible to reliably detect an empty argument list in all cases, so BOOST_PP_VARIADIC_SIZE() returns 1 for an empty argument list. Take a look at this thread [1] for an explanation and a workaround.
Hmm, am I trying to solve the wrong problem?
The briefest explanation of what I want is to take a macro argument list with zero or more arguments and construct a function argument list with one more (initial) argument than that.
I just need MACRO() to expand to function(first), but MACRO(anything) to expand to function(first, anything).
Is there some more reliable way than BOOST_PP_COMMA_IF(BOOST_PP_VARIADIC_SIZE()) to make the comma appear only when required?
The preprocessor only really understand commas and parentheses. So, as far as it's concerned, the definition of "the size of a variadic argument list" is "the number of (top-level) commas in the list plus one".
You want to make an additional distinction - in the case where there are no top-level commas, you want to distinguish between the case where there are some tokens present, and the case where there are none. As Paul Mensonides explains in the thread I linked to, this is not possible in the general case.
It *is* possible to write a version of BOOST_PP_VARIADIC_SIZE() that works the way you want in many cases, quite possibly including your use cases. I believe Boost.Preprocessor does not provide such a version on principle (because it would not work in all cases). However, nothing stops you from using your own version that does this. Gennadiy Rozental posted such a version in the first post of the thread I linked to.
Something like: #include <boost/preprocessor/control/iif.hpp> #include <boost/preprocessor/variadic/size.hpp> #include <boost/variadic_macro_data/vmd_is_empty.hpp> #define ALTERNATIVE_VARIADIC_SIZE(...) BOOST_PP_IIF(BOOST_VMD_IS_EMPTY(__VA_ARGS__), 0, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) ( The above should be all on one line ) will give an alternate macro for finding out the size of the variadic data. But of course it is flawed because there is no perfect way to test for emptiness. I think the VMD macro for "emptiness", which is taken directly from a post by Paul Mensonides with some minor tweaks for VC++, is pretty good. But Paul did not want to include it in Boost PP, probably because he did not want to rely on a macro with flaws and because variadic data, by its nature, is never considered "empty", ie. is never considered to have a 0 size. I do understand Gennady's POV in his post in the link you gave as well as the OP's query.

On Dec 11, 2012, at 10:19 AM, Nat Linden wrote:
In fact, when I change the definition of the intercept() macro below to:
#define intercept(FIRST, ARGS...) \ targetfunc(transform(FIRST) \ , boost::lexical_cast<std::string>(BOOST_PP_VARIADIC_SIZE(ARGS)))
I see that BOOST_PP_VARIADIC_SIZE() expands to 1 for intercept("first") as well as for intercept("first", "second").
Is it possible to achieve what I want with my present suite of compilers? Is there some workaround we could use until (at some point in the indefinite future) we've upgraded all our compilers so that even the oldest of them supports variadic functions?
Perhaps something like the following will do what you need? #define intercept(ARGS...) \ BOOST_PP_IIF( \ BOOST_PP_EQUAL(1, BOOST_PP_VARIADIC_SIZE(ARGS)), \ intercept_1, \ intercept_many)(ARGS) where the definitions of intercept_1 and intercept_many should be obvious.
participants (4)
-
Edward Diener
-
Kim Barrett
-
Nat Linden
-
Nathan Ridge