- [[#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 64 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. difficulties (haven’t tested higher numbers. 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 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
.
Any number of fixed args may be used, however the fixed args MUST be parenthesised in the macro call. 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__)
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 64 __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 <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 127 > pp_iter.h
When called without any arguments, the default value of 64 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 "enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
TAGGED_ENUM(FavouritePiper);
… which expands to …
#include "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 "enum.h"
#define FavouritePiperIds \
WILLIE_CLANCY, \
SEAMUS_ENNIS, \
TOMMY_RECK
TAGGED_ENUM_TYPE(FavouritePiper);
… which expands to …
#include "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)
#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"; }
- 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.