Struct wrappers. Define your optional parameters as fields of a struct.
struct foo_args {
const char* title = "";
int year = 1900;
float percent = 0.0;
};
void foo(int a, int b, const foo_args& args = foo_args())
{
printf("title: %s\nyear: %d\npercent: %.2f\n",
args.title, args.year, args.percent);
}
int main()
{
foo_args args;
args.title = "foo title";
args.percent = 99.99;
foo(1, 2, args);
/* Note: in pure C brace initalizers could be used instead
but then you loose custom defaults -- non-initialized
fields are always zero.
foo_args args = { .title = "foo title", .percent = 99.99 };
*/
return 0;
}
Proxy objects. Arguments are stored in a temporary struct which can be modified with chained setters.
struct foo {
// Mandatory arguments
foo(int a, int b) : _a(a), _b(b) {}
// Optional arguments
// ('this' is returned for chaining)
foo& title(const char* title) { _title = title; return *this; }
foo& year(int year) { _year = year; return *this; }
foo& percent(float percent) { _percent = percent; return *this; }
// Do the actual call in the destructor.
// (can be replaced with an explicit call() member function
// if you're uneasy about doing the work in a destructor)
~foo()
{
printf("title: %s\nyear: %d\npercent: %.2f\n", _title, _year, _percent);
}
private:
int _a, _b;
const char* _title = "";
int _year = 1900;
float _percent = 0.0;
};
int main()
{
// Under the hood:
// 1. creates a proxy object
// 2. modifies it with chained setters
// 3. calls its destructor at the end of the statement
foo(1, 2).title("foo title").percent(99.99);
return 0;
}
Note: the boilerplate can be abstracted out to a macro at the expense of readability:
#define foo_optional_arg(type, name, default_value) \
public: foo& name(type name) { _##name = name; return *this; } \
private: type _##name = default_value
struct foo {
foo_optional_arg(const char*, title, "");
foo_optional_arg(int, year, 1900);
foo_optional_arg(float, percent, 0.0);
...
Variadic functions. This obviously is type-unsafe and requires knowledge of type promotions to get right. It is, however, available in pure C if C++ is not an option.
#include <stdarg.h>
// Pre-defined argument tags
enum foo_arg { foo_title, foo_year, foo_percent, foo_end };
void foo_impl(int a, int b, ...)
{
const char* title = "";
int year = 1900;
float percent = 0.0;
va_list args;
va_start(args, b);
for (foo_arg arg = (foo_arg)va_arg(args, int); arg != foo_end;
arg = (foo_arg)va_arg(args, int))
{
switch(arg)
{
case foo_title: title = va_arg(args, const char*); break;
case foo_year: year = va_arg(args, int); break;
case foo_percent: percent = va_arg(args, double); break;
}
}
va_end(args);
printf("title: %s\nyear: %d\npercent: %.2f\n", title, year, percent);
}
// A helper macro not to forget the 'end' tag.
#define foo(a, b, ...) foo_impl((a), (b), ##__VA_ARGS__, foo_end)
int main()
{
foo(1, 2, foo_title, "foo title", foo_percent, 99.99);
return 0;
}
Note: In C++ this can be made type-safe with variadic templates. The run-time overhead will be gone at the expense of slower compilation times and binary bloat.
boost::parameter. Still a third-party library, albeit more established lib than some obscure github repo. Drawbacks: template-heavy.
#include <boost/parameter/name.hpp>
#include <boost/parameter/preprocessor.hpp>
#include <string>
BOOST_PARAMETER_NAME(foo)
BOOST_PARAMETER_NAME(bar)
BOOST_PARAMETER_NAME(baz)
BOOST_PARAMETER_NAME(bonk)
BOOST_PARAMETER_FUNCTION(
(int), // the return type of the function, the parentheses are required.
function_with_named_parameters, // the name of the function.
tag, // part of the deep magic. If you use BOOST_PARAMETER_NAME you need to put "tag" here.
(required // names and types of all required parameters, parentheses are required.
(foo, (int))
(bar, (float))
)
(optional // names, types, and default values of all optional parameters.
(baz, (bool) , false)
(bonk, (std::string), "default value")
)
)
{
if (baz && (bar > 1.0)) return foo;
return bonk.size();
}
int main()
{
function_with_named_parameters(1, 10.0);
function_with_named_parameters(7, _bar = 3.14);
function_with_named_parameters( _bar = 0.0, _foo = 42);
function_with_named_parameters( _bar = 2.5, _bonk= "Hello", _foo = 9);
function_with_named_parameters(9, 2.5, true, "Hello");
}
On a closing note, I wouldn't use this kwargs library simply because there is a number of good enough alternatives in C++ to achieve the same. I personally would opt for 1. or 2. from the (non-exhaustive) list above.