When dealing with functions that have lots of optional parameters, or at least for which resonable defaults are readily available, it's often a bit of a frustration in C++. Generally defaults are specified during a function as in:
double BinarySearch(std::function<double(double> fn,
int max_depth = 16, double epsilon = 1e-9,
double lower_bound = 0, double upper_bound = 100);
And then if we call BinarySearch
with only one parameter then the call will
use the default values for the rest. But what if I want to specify custom
bound, but use the defaults for the other parameters? Admittedly, this is a
contrived example since bounds are less likely to be optional then the others,
and we could reorder them better going from most-likely-to-be-specified to
least, but it's easy to see how something more flexible would be desirable.
Consider then the following two code snippets. Which is more readable?
First snippet:
double solution = BinarySearch(fn, 0, 100);
Second snippet:
double solution = BinarySearch(fn, lower_bound = 0, upper_bound = 100);
I really like the way that optional arguments work in python with kwargs
. I'd
love to have that same kind of functionality in C++. kwargs.h
implements
one mechanism of achieving this.
kwargs
takes advantage of variadic templates in C++ to build
up a single data structure which contains all of the optional parameters
(I'll call this a "parameter pack"). Each optional parameter of type T
is
stored in a structure of type Arg<tag,T>
where tag
is a unique numeric
identifier associated with a particular optional argument key.
The parameter pack data structure derives from Arg<tag,T>
for each (tag,T)
pair that shows up in the list of optional arguments.
Overloading of the equals (=
) operator gives us an opportunity for building
the (tag,T)
pairs within the parameter list of the function call.
See more documentation and examples on github pages.
#include <iostream>
#include "kwargs.h"
// these are tags which will uniquely identify the arguments in a parameter
// pack
enum Keys {
c_tag,
d_tag
};
// global symbols used as keys in list of kwargs
kw::Key<c_tag> c_key;
kw::Key<d_tag> d_key;
// a function taking kwargs parameter pack
template <typename... Args>
void foo(int a, int b, Args... kwargs) {
// first, we construct the parameter pack from the parameter pack
kw::ParamPack<Args...> params(kwargs...);
std::cout << "foo:\n--------"
<< "\na: " << a
<< "\nb: " << b
// We can attempt to retrieve a key while providing a default fallback value.
// If c_key is in kwargs then this will return the value associated with
// that key, and will have the correct type. Note that the type of the default
// parameter in this case is const char*.
<< "\nc: " << kw::Get(params,c_key,"null");
// We can also do stuff conditionally based on whether or not arg exists in
// the param pack. We still need to provide a default value, since we need to
// know the return type of the Get function when the key is not in kwargs.
// In this case, the default value wont ever be used at runtime.
if( kw::ContainsTag<c_tag,Args...>::result ) {
std::cout << "\nd: " << kw::Get(params,d_key,0);
}
std::cout << "\n\n";
}
int main( int argc, char** argv )
{
foo(1, 2);
foo(1, 2, c_key=3);
foo(1, 2, c_key=3, d_key=4);
foo(1, 2, d_key=4);
}