- [[#usage-example–auto-generation-of-string-descriptors-for-an-enum-type][Usage example
- auto-generation of string descriptors for an enum type]]
This repository includes the following:
- pp_iter.h
- Some C macros (and an associated code generator) to count and iterate over variadic macro arguments.
- pp_enum.h
- A set of macros building on those defined in pp_iter.h to facilitate auto-generation of string representations of members of an enum type.
The included pp_iter.h
includes macros to handle up to 63 arguments. Behaviour beyond this is undefined.
The Ruby code generator script can be used to generate a header supporting larger numbers of variadic arguments if required.
The C99 and C11 standard specifies that a compiler must handle a program with a macro with 127 arguments, so behaviour beyond this limit will be undefined. In theory, this only guarantees operation of the counting macros up to about 63 arguments, at least using this implementation.
In my experience, MSVC appears to enforce the 127-argument limit, whereas gcc handled 512 without difficulties (didn’t test any higher arg count).
This implementation has been tested with clang and with numerous versions of gcc (desktop and embedded) without issue. I haven’t tested recently with MSVC (and life’s too short…) but if you encounter issues, the non-recursive
branch of this repository includes a version of the ruby generator that can optionally not use tail recursion and generate a functional but much more verbose version of pp_iter.h
. Check out that branch for further information.
This family of macros is intended to allow the transformation of a __VA_ARGS__
list into a single C expression OR one C expression per argument. In the latter case, the transformation macro should include the concluding semi-colon. The two indexed macro variants use 0-based indexing.
Iterates over a set of variadic macro arguments and applies a provided transformation macro (TF(ARG)
) to each argument ARG
.
Iterates over a set of variadic macro arguments and applies a provided transformation macro TF(ARG, IDX)
to each argument ARG
and index IDX
.
Iterates over a set of variadic macro arguments and applies a provided transformation macro TF(FIXED_ARG1, [...additional fixed args,], ARG, IDX)
to each argument ARG
and index IDX
.
I.e. PP_PAR_EACH_IDX(TF, (FIXED_ARG1), VAR_ARG_1, VAR_ARG_2)
will work, but PP_PAR_EACH_IDX(TF, FIXED_ARG1, VAR_ARG_1, VAR_ARG_2)
won’t.
I use it when mocking dependencies (using ceedling and fake function framework) for shorthand verification of function calls, i.e.
TEST_ASSERT_CALLED_WITH(RgbPixel_render, &rendered_pixel, TEST_COLOUR, TEST_INTENSITY);
… rather than …
TEST_ASSERT_EQUAL(1, RgbPixel_render_fake.call_count);
TEST_ASSERT_EQUAL(&rendered_pixel, RgbPixel_render_fake.arg0_val);
TEST_ASSERT_EQUAL(TEST_COLOUR, RgbPixel_render_fake.arg1_val);
TEST_ASSERT_EQUAL(TEST_INTENSITY, RgbPixel_render_fake.arg2_val);
… which is facilitated by ....
#define _FFF_VERIFY_PARAMETER_(FN, VAL, IDX) TEST_ASSERT_EQUAL(VAL, FN##_fake.arg##IDX##_val);
#define TEST_ASSERT_CALLED_WITH(FN, ...) \
TEST_ASSERT_CALLED(FN); \
PP_PAR_EACH_IDX(_FFF_VERIFY_PARAMETER_, (FN), __VA_ARGS__)
A variant of PP_EACH that comma-separates the results of transformation.
This is useful if you need to generate a list of arguments to a function.
pp_iter.h
uses PP_EACH in a related context define an enum type, however it generates a trailing ‘,’, which is valid syntax for an enum definition or array initalisation, but not for a function call.
Contributed by OndrejPopp
The original implementation required separate parameterised macro sets to be defined for a given number of fixed arguments, but the adoption of nested bracing has allowed them to be eliminated.
Deprecated syntax | New syntax |
---|---|
PP_1PAR_EACH_IDX(TF, FARG, …) | PP_PAR_EACH_IDX(TF, (FARG), …) |
PP_2PAR_EACH_IDX(TF, FARG1, FARG2, …) | PP_PAR_EACH_IDX(TF, (FARG1, FARG2), …) |
Any use of the deprecated syntax should ideally be replaced in source, however the generator does support definition of wrapper macros if required.
This repository includes a pre-generated header to handle up to 63 __VA_ARGS__
. A header to handle an arbitrary number of arguments may be generated using the included generator script (written in ruby), as follows:
ruby pp_iterators.rb --limit <NARGS>
By default, the script just prints the header content to the console, so you’ll want to redirect to file.
e.g. for up to 127 args
ruby pp_iterators.rb --limit 127 > pp_iter.h
When called without any arguments, the default value of 63 will be used.
The generator provides a set of methods which may be used in 3rd party code generators. These support generation of the macros described above as well as variants (e.g. macro sets with an arbitrary number of fixed args, and some variants of the argument counting macros).
The argument counting macros use some common definitions, or see the fake function framework for a usage example.
ppi = PPIterators.new(127);
puts <<~EOH
# Define the counting macros PP_NARG and PP_NARG_MINUS2_N
#{ppi.narg_common}
#{ppi.narg}
#{ppi.narg_minus(2)}
# Define PP_EACH(...)
#{ppi.each}
EOH
The file enum.h uses PP_EACH
to support autogeneration of textual descriptions of enum members. This saves some repetition and eliminates the risk of forgetting to update the tag when adding/re-arranging members.
#include "pp_enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
TAGGED_ENUM(FavouritePiper);
… which expands to …
#include "pp_enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
enum FavouritePiper {
WILLIE_CLANCY,
SEAMUS_ENNIS,
TOMMY_RECK,
FavouritePiper_COUNT
};
char const * FavouritePiper_asCString(int id);
#include "pp_enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
TAGGED_ENUM_TYPE(FavouritePiper);
… which expands to …
#include "pp_enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
typedef enum {
WILLIE_CLANCY,
SEAMUS_ENNIS,
TOMMY_RECK,
FavouritePiper_COUNT
} FavouritePiper;
char const * FavouritePiper_asCString(int id);
(This uses the PP_EACH
macro)
Assuming my_tagged_enum.h
contains the listing provided above for either the typed or untyped enum example…
#include "my_tagged_enum.h"
ENUM_DESCRIBE(FavouritePiper);
… which expands to …
#include "my_tagged_enum.h"
static char const * FavouritePiper_TAGS[] = {
"WILLIE_CLANCY",
"SEAMUS_ENNIS",
"TOMMY_RECK",
};
char const * FavouritePiper_asCString(int id) { return id < FavouritePiper_COUNT ? FavouritePiper_TAGS[id] : "UNDEFINED"; }
This sacrifices the protection against re-arrangement of members, but should at least ensure that your compiler warns you if the number of tags doesn’t match the number of enum members.
#include "my_tagged_enum.h"
ENUM_DESCRIBE_EXPLICIT(FavouritePiper,
"Willie Clancy",
"Seamus Ennis",
"Tommy Reck"
);
… which expands to …
#include "my_tagged_enum.h"
static char const * FavouritePiper_TAGS[] = {
"Willie Clancy",
"Seamus Ennis",
"Tommy Reck"
};
char const * FavouritePiper_asCString(int id) { return id < FavouritePiper_COUNT ? FavouritePiper_TAGS[id] : "UNDEFINED"; }
There are some basic unit tests here: ./test/pp_iter_test.cpp.
mkdir -p build
pushd build
cmake ..
cmake --build .
popd
./build/tests
- I initially encountered the variadic macro counting logic in this post by Laurent Deniau. His solution was refined by arpad. and zhangj to handle the no-argument case.
- The (preferred) recursive implementations of PP_EACH, PP_EACH_IDX and PP_PAR_EACH_IDX are based on an excellent series of posts by Saad Ahmad.
- The non- (or semi-) recursive PP_EACH implementation is based on this blog post by Daniel Hardman.
- The non-recursive PP_EACH_IDX and PP_PAR_EACH_IDX macro implementations extend the non-recursive PP_EACH implementation described in this (anonymous) blog post.
- The MSVC macro expansion fix was lifted from the excellent fake function framework.