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.
How does it work
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.
Examples
Example 1
Source
16 template <
typename... Args>
17 void foo(
int a,
int b, Args... kwargs) {
21 std::cout <<
"foo:\n--------"
28 <<
"\nc: " <<
kw::Get(params,c_key,
"null");
34 std::cout <<
"\nd: " <<
kw::Get(params,d_key,0);
40 int main(
int argc,
char** argv )
44 foo(1, 2, c_key=3, d_key=4);
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
TypeOfTagDefault< Tag, Default, Args...>::Result Get(ParamPack< Args...> &pack, Default d)
given a parameter pack, retrieves and return sthe parameter tagged with tag, or else returns a defaul...
template meta-function contains a static boolean variable 'result' which is true if Tag is in the lis...
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Output
foo:
--------
a: 1
b: 2
c: null
foo:
--------
a: 1
b: 2
c: 3
d: 0
foo:
--------
a: 1
b: 2
c: 3
d: 4
foo:
--------
a: 1
b: 2
c: null
Example 2
Source
22 template <
typename... Args>
23 void foo(
int a,
int b, Args... kwargs) {
26 std::cout <<
"foo:\n-------"
31 <<
"\nc: " << kw::Get<0>(params,
"null")
33 <<
"\nd: " <<
kw::Get(params,test::d_key,
"null")
38 int main(
int argc,
char** argv )
46 foo(1, 2, test::c_key = 3);
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
TypeOfTagDefault< Tag, Default, Args...>::Result Get(ParamPack< Args...> &pack, Default d)
given a parameter pack, retrieves and return sthe parameter tagged with tag, or else returns a defaul...
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Output
foo:
-------
a: 1
b: 2
c: null
d: null
foo:
-------
a: 1
b: 2
c: 3
d: null
foo:
-------
a: 1
b: 2
c: 3
d: null
foo:
-------
a: 1
b: 2
c: null
d: 3
Example 3
Source
16 Test(
const Test&
other) { Construct(); }
17 Test() { Construct(); }
20 std::cout <<
"Destroyed object " << obj_id_ <<
"\n";
23 int GetId()
const {
return obj_id_; }
28 std::cout <<
"Created object " << obj_id_ <<
"\n";
34 int Test::n_objs_ = 0;
37 std::ostream&
operator<<( std::ostream& out,
const Test& test ){
43 std::ostream&
operator<<( std::ostream& out,
const Test* test ){
44 out <<
"*" << test->GetId();
48 template <
typename... Args>
49 void foo(
int a,
int b, Args... kwargs) {
52 std::cout <<
"foo:\n----"
55 <<
"\ne: " <<
kw::Get(params,c_key,
"null")
56 <<
"\nf: " <<
kw::Get(params,d_key,
"null")
60 int main(
int argc,
char** argv )
64 foo(1, 2, c_key=Test());
77 foo(1, 2, d_key=
kw::Ref(test_obj));
82 foo(1, 2, d_key=&test_obj);
RefWrap< T > Ref(T &v)
forces an argument to be passed by reference
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
TypeOfTagDefault< Tag, Default, Args...>::Result Get(ParamPack< Args...> &pack, Default d)
given a parameter pack, retrieves and return sthe parameter tagged with tag, or else returns a defaul...
std::ostream & operator<<(std::ostream &out, const freetype::Untag &u)
const ConstRefWrap< T > ConstRef(const T &v)
forces an argument to be passed by const reference
BinaryKey other(const BinaryKey &key)
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Output
Created object 0
Created object 1
Created object 2
Destroyed object 1
Created object 3
Created object 4
Destroyed object 3
Created object 5
foo:
----
a: 1
b: 2
e: 5
f: null
Destroyed object 5
Destroyed object 4
Destroyed object 2
Destroyed object 0
Created object 6
foo:
----
a: 1
b: 2
e: null
f: 6
Destroyed object 6
Created object 7
foo:
----
a: 1
b: 2
e: null
f: 7
foo:
----
a: 1
b: 2
e: null
f: 7
foo:
----
a: 1
b: 2
e: null
f: *7
Destroyed object 7