Skip to content

Some C macros (and associated code generators) to count and iterate over variadic macro arguments

License

Notifications You must be signed in to change notification settings

cormacc/va_args_iterators

Repository files navigation

__VA_ARGS__ counting and iteration macros

Table of Contents

[[#usage-example–auto-generation-of-string-descriptors-for-an-enum-type][Usage example
auto-generation of string descriptors for an enum type]]

Description

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).

Compiler compatibility

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.

Usage instructions

Macros

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.

PP_EACH(TF, …)

Iterates over a set of variadic macro arguments and applies a provided transformation macro (TF(ARG)) to each argument ARG.

PP_EACH_IDX(TF, …)

Iterates over a set of variadic macro arguments and applies a provided transformation macro TF(ARG, IDX) to each argument ARG and index IDX.

PP_PAR_EACH_IDX(TF, (FIXED_ARGS), …)

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__)

PP_COMMA_EACH(TF, …)

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

Deprecated macros

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 syntaxNew 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.

Generator

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

Usage example :: auto-generation of string descriptors for an enum type

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.

my_tagged_enum.h

Untyped enum

#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);

Typed enum

#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);

my_tagged_enum.c

Automatic tag generation

(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"; }

Custom tag definition

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"; }

Unit tests

There are some basic unit tests here: ./test/pp_iter_test.cpp.

Building unit tests

mkdir -p build
pushd build
cmake ..
cmake --build .
popd

Running unit tests

./build/tests

References / prior art

  • 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.

About

Some C macros (and associated code generators) to count and iterate over variadic macro arguments

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published