I was recently working with a struct with a lot of individual options. Most of the time the default options are fine, however almost every instantiation will need to override at least one. If your project is bound to not-so-modern C++, this may have undesirable results:
struct Foo
{
// Required:
int id;
char* name;
// Optional:
float foo = 1.41f;
long bar = 1L << 40;
bool baz = true;
};
Foo twelve = { 12, "twelve", 1.41f, 1024 };I only wanted to override bar, but now I’ve had to duplicate a seemingly-magic number for foo. If I were to change foo’s default, I’d have to look at every instantiation site. Ideally what I’d want is something like C++20’s designated initialization, or C#’s object initializers:
Foo twelve {
.id = 12, // This won't call explicit ctor!
.name = "twelve",
.bar = 1024 // < I only wanted position independence here
};Foo twelve = new Foo(12, "twelve")
{
bar = 1024
};
// OR using named function arguments
Foo one = new Foo(1, "one", bar=32);One trick we can use is declaring a lambda that we immediately execute:
Foo twelve = ([]() { // We can also declare this constexpr as of C++17
Foo out = { 12, "twelve" };
out.bar = 1024;
return out;
})();This achieves our original goal, but makes instantiation of many Foos at once excessively verbose.
However, this at least follows a pattern. Perhaps we could generalize it?
Preprocessor sorcery
Foo twelve = CONSTRUCT_WITH_NAMED_ARGS(Foo, (12, "twelve"), (bar=1024));
#define EXPAND(...) __VA_ARGS__
#define CONSTRUCT_WITH_NAMED_ARGS(Type, POSITIONAL_ARGS, NAMED_ARGS) \
([]() { \
Type out{ EXPAND POSITIONAL_ARGS }; /* This will call constructor if present */ \
/* foreach arg in NAMED_ARGS: assign out.arg */ \
return out; \
})()This could work if we had a way to loop over arguments in a macro. Further blocking this, macros are designed for text and token processing, and the only part of the preprocessor we can really do math is in #if/#elif directives. There isn’t even a builtin way to count how many arguments are in __VA_ARGS__. If we write 30+1 in a macro, the literal 30+1 will be passed to the compiler.
However, we just need to increment or decrement a counter. If we could count how many elements in a list, we could approach this using a bit of recursion and concatenation.
#define CONSTRUCT_WITH_NAMED_ARGS_SET_MEMBER(assign_expression) out.assign_expression; // Example: bar=1024 becomes out.bar=1024;
#define CONSTRUCT_WITH_NAMED_ARGS(Type, POSITIONAL_ARGS, NAMED_ARGS) \
([]() { \
Type out{ EXPAND POSITIONAL_ARGS }; /* This will call constructor if present */ \
FOREACH(CONSTRUCT_WITH_NAMED_ARGS_SET_MEMBER, COUNT(NAMED_ARGS), NAMED_ARGS) \
return out; \
})()
#define HEAD(X, ...) X
#define TAIL(X, ...) __VA_ARGS__
#define FOREACH(DO_SOMETHING, N, LIST) FOREACH_##N##_ELEMENTS(DO_SOMETHING, LIST)
#define FOREACH_0_ELEMENTS(DO_SOMETHING, LIST)
#define FOREACH_1_ELEMENTS(DO_SOMETHING, LIST) DO_SOMETHING(HEAD(LIST))
#define FOREACH_2_ELEMENTS(DO_SOMETHING, LIST) DO_SOMETHING(HEAD(LIST)) FOREACH_1_ELEMENTS(DO_SOMETHING, TAIL(LIST))
#define FOREACH_4_ELEMENTS(DO_SOMETHING, LIST) DO_SOMETHING(HEAD(LIST)) FOREACH_2_ELEMENTS(DO_SOMETHING, TAIL(LIST))
// ... add as many cases as you need ...This only leaves how to count the elements in a list:
#define COUNT_DETAIL(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define COUNT(...) COUNT_DETAIL(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)
// Example:
// COUNT(a, b, c) becomes COUNT_DETAIL(a, b, c, 8, 7, 6, 5, 4, 3, 2, 1)
// Which passes as the parameters _1,_2,_3,_4,_5,_6,_7,_8, N, ...
// This tells us our count ^With one last macro to make this easy on the eyes, we can now use our macro to concisely instantiate Foo with whatever named parameters we need:
#define MAKE_FOO(id, name, ...) CONSTRUCT_WITH_NAMED_ARGS(Foo, (id, name), (__VA_ARGS__))
Foo twelve = MAKE_FOO(12, "twelve", bar=1024);
Foo one = MAKE_FOO(1, "one", bar=32);
Foo neg_one = MAKE_FOO(-1, "negative one", baz=false, bar=4); // We can even put them out of order!