Variadic Macros
Variadic macros are supported by a number of compilers. They are
macros of the form:
#define SOME_MACRO(ZeroOrMoreParameters,...) macro expansion possible specifying __VA_ARGS__
The '...' in the parameter list represents the variadic data when the
macro is invoked and the __VA_ARGS__ in the expansion represents the
variadic data in the expansion of the macro. Variadic data is of the form
of 1 or more preprocessor tokens separated by commas.
The '...' must be the last parameter in the macro definition and there may
be 0 or more non-variadic parameters preceding it.
In the expansion of the macro __VA_ARGS__ may be specified 0 or more times
to represent the variadic data. The variadic data in the expansion is a
comma separated list of preprocessor tokens representing the variadic data
which the invoker of the macro enters as the last arguments to the macro.
Example - Creating and invoking a variadic macro.
#define INITIALIZE_INT_ARRAY(array_name,...) \
static int array_name[] = { __VA_ARGS__ }; \
/**/
INITIALIZE_INT_ARRAY(myname,45,789,33510,9346,2)
Preprocessor
Library Support
The library offers support for variadic macros for those
compilers
which support the feature. The library can automatically detect whether
a compiler supports variadic macros and sets the macro
BOOST_PP_VARIADICS accordingly to 1 if the compiler supports variadic
macros or 0 if the compiler does not support variadic macros.
The end-user can #define BOOST_PP_VARIADICS to 1 or 0 himself in a
translation unit, before including any preprocessor header files, to
prevent the library from attempting to detect whether the compiler
supports variadic macros. This has the effect of manually turning on or
off variadic macro support in the library. Of course if one manually
turns on variadic macro support in the library, and one's compiler does
not support variadic macros, functionality in the library which uses
variadic macros will fail with error messages from the compiler.
When BOOST_PP_VARIADICS is 1, the library offers some extended
functionality
by using variadic macros, and also offers extended support for working
with variadic data.
Visual C++ has a
few quirks related to variadic macros which require the end-user to code
slightly differently. When BOOST_PP_VARIADICS is 1 and Visual C++ is the
compiler BOOST_PP_VARIADICS_MSVC is 1, else when BOOST_PP_VARIADICS is 1
and Visual C++ is not the compiler BOOST_PP_VARIADICS_MSVC is 0. When
BOOST_PP_VARIADICS is 0 then BOOST_PP_VARIADICS_MSVC is not defined.
In this way the end-user, when using variadic macros, can test for the
presence of Visual C++ as the compiler and code accordingly.
Support for working with variadic
data is largely centered on being able to convert variadic data to
other library data types, since the
functionality for working with those Boost preprocessor library data
types is much greater than that for working with variadic data directly.
Notation For Variadic Macros
In the documentation, headers which have variadic macros,
and
variadic macros themselves, have a notation of '(v)' appended to them.
For the variadic macros themselves this signifies that
BOOST_PP_VARIADICS must be 1 for those variadic macros to be usable.
For variadic macros which have a non-variadic equivalent, the
non-variadic equivalent will be used if BOOST_PP_VARIADICS is set to 0.
Extended Functionality Using Variadic Macros
Some macros in the library offer extended
functionality through the use of variadic macros.
The variadic macro version offers the same functionality
as the non-variadic version, but because of the ability of the variadic
parameters to encompass a variable number of arguments, it also offers
an enhanced syntax using the same macro name.
The macros in the library which offer this enhanced functionality are
all
centered on tuple manipulation. With variadic
macros it is
possible to
manipulate tuples without having to know the size of the tuple. So
while the invoker can still specify the size when using tuple macro
functionality, there are variadic versions of each of the tuple macros,
with the exact same name as the non-variadic macro, where the size need
not be specified.
Extended Support For Variadic Data
The library offers extended support for working with
variadic data
which goes beyond the functionality offered by the C++ specification
for variadic macros. It does this through preprocessor programming and
by using some of the other functionality in the library itself. Header
and macro names
in the library which offer extended support for working with variadic
data, and need the compiler to support variadic macros, are marked with
a (v) to indicate a variadic macro.
The form of the functionality which the library offers is centered on
two macros which work with variadic data itself, and a set of macros
which convert between variadic data and other library data
types.
The two macros are BOOST_PP_VARIADIC_ELEM and BOOST_PP_VARIADIC_SIZE,
which respectively return a particular token of variadic data and the
number of tokens of variadic data.
The macros for converting variadic data to the library's data types are
BOOST_PP_VARIADIC_TO_ARRAY, BOOST_PP_VARIADIC_TO_LIST,
BOOST_PP_VARIADIC_TO_SEQ, and BOOST_PP_VARIADIC_TO_TUPLE.
All of these macros need compiler support for variadic data and only
exist if BOOST_PP_VARIADICS is 1.
The remaining four macros, which convert from a library data type
to comma-separated preprocessor tokens, which is the form of
variadic data, do not need compiler support for variadic
macros. These functions are BOOST_PP_ARRAY_ENUM, BOOST_PP_LIST_ENUM,
BOOST_PP_SEQ_ENUM, and BOOST_PP_TUPLE_ENUM. However if one wishes to
use this variadic data reliably as arguments to other macros, one needs
variadic macro support.
C++20 Support For Variadic Macros
In the C++20 specification there is a new construct which can be
used in the expansion of a variadic macro, called __VA_OPT__. This
construct when used in the expansion of a variadic macro is followed
by an opening paranthesis ('('), preprocessor data, and a closing
parenthesis ('}'). When the variadic data passed by the invocation
of a variadic macro is empty, this new construct expands to nothing.
When the variadic data passed by the invocation of a variadic macro
is not empty, this new construct expands to the preprocessor data
between its opening and closing parentheses.
This library offers support for this new C++20 construct by automatically
detecting whether this new construct is supported by the compiler's
preprocessor when using the library. The library macro which detects
support for the __VA_OPT__ construct is called BOOST_PP_VARIADIC_HAS_OPT.
This is a function-like macro which takes no parameters and returns
1 if the compiler is working in C++20 mode and supports the __VA_OPT__
construct, while otherwise it returns 0.
Using a Tuple Instead of an Array
An array as a preprocessor data type is a two-element tuple where the
first element is the array size and the second element is a tuple which
constitutes the array data. Because a tuple knows its own size when the
compiler supports variadic macros, there is no reason to use the array preprocessor
data type as opposed to the tuple preprocessor data type; the tuple data
type now has all of the functionality which the array data type has and is
syntactically easier to use. With variadic macro support, which is now
officially part of the latest C++ standard, the preprocessor array data
type is essentially obsolete for conforming C++ compilers. Only if your
compiler does not support variadic macros is the preprocessor array data
type still useful.
Using Variadic Data
Variadic data exists in the
form of comma-separated preprocessor tokens. This is the case whether
the variadic data comes from the __VA_ARGS__ of a variadic macro, from
the conversion of a library's data type to variadic data, or the
manual construction of comma-separated preprocessing tokens by the
programmer writing a macro.
The easiest way to work with
variadic data internally is to convert it to a library data type.
Library data types, whether an array, list,
sequence,
or tuple, have a rich set of functionality for
manipulating
data whereas
variadic data functionality in the library only allows one to access
the variadic data as a whole or to access a single token of the
variadic data at a time.
The user of the library still may
choose to pass variadic data back into internal macros rather than
convert it to other library data types. There is no problem passing
variadic data as a whole to variadic macros as the last parameter of
the macro. However:
Attempting to pass
variadic data as a
whole directly into a non-variadic macro is not guaranteed to work and
may fail.
This occurs because of a preprocessor weakness in a number
of compilers, currently most notably Visual C++. Even passing variadic
data as arguments to a non-variadic macro, when it is not represented
in
the form of __VA_ARGS__, may fail with certain compilers.
What follows are very simple examples, showing how variadic data can be
passed to a non-variadic macro.
First an example of what NOT to do.
Example - Passing variadic data as a whole to a
non-variadic
macro. DO NOT DO.
#define MACRO_ARG_2(x,y) BOOST_PP_ADD(x,y)
#define VAR_MACRO(...) __VA_ARGS__
/* The following should not be done and is not guaranteed to work with compilers. */
int xx = MACRO_ARG_2(VAR_MACRO(2,3));
There are two ways to pass variadic data to a non-variadic
macro.
The
first of these is to pass the individual tokens of the variadic data
separately to the non-variadic macro using the BOOST_PP_VARIADIC_ELEM
macro in the library.
Example - Passing individual variadic data tokens to
a
non-variadic macro.
#define MACRO_ARG_2(x,y) BOOST_PP_ADD(x,y)
#define VAR_MACRO(...) __VA_ARGS__
/* The following will work correctly */
int xx = MACRO_ARG_2
(
BOOST_PP_VARIADIC_ELEM(0,VAR_MACRO(2,3)),
BOOST_PP_VARIADIC_ELEM(1,VAR_MACRO(2,3))
);
The second way is to use a macro in the library called
BOOST_PP_OVERLOAD.
This macro allows one to "overload" a variadic macro to non-variadic
macros of different numbers of parameters, using a common prefix.
Example - Passing variadic data as a whole to
BOOST_PP_OVERLOAD
and on to a non-variadic macro.
#define MACRO_ARG_2(x,y) BOOST_PP_ADD(x,y)
#define VAR_MACRO(...) __VA_ARGS__
/* The following will work correctly */
int xx = BOOST_PP_OVERLOAD(MACRO_ARG_,VAR_MACRO(2,3))(VAR_MACRO(2,3));
/* For Visual C++ it is necessary to do this */
int xx =
BOOST_PP_CAT(BOOST_PP_OVERLOAD(MACRO_ARG_,VAR_MACRO(2,3))(VAR_MACRO(2,3)),BOOST_PP_EMPTY());
Although these techniques will work when passing variadic
data to
non-variadic macros, it is much better and less problematical to
work internally with the existing library data types and to only use
variadic
macros as an interface for end-users when there is a need to have a
macro which takes a
variable number of parameters.
See Also
© Copyright
Edward Diener
2011,2013,2016