cheshirekow  v0.1.0
Python-style kwargs in C++

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

1 #include <iostream>
2 #include "kwargs/kwargs.h"
3 
4 // these are tags which will uniquely identify the arguments in a parameter
5 // pack
6 enum Keys {
7  c_tag,
8  d_tag
9 };
10 
11 // global symbols used as keys in list of kwargs
12 kw::Key<c_tag> c_key;
13 kw::Key<d_tag> d_key;
14 
15 // a function taking kwargs parameter pack
16 template <typename... Args>
17 void foo(int a, int b, Args... kwargs) {
18  // first, we construct the parameter pack from the parameter pack
19  kw::ParamPack<Args...> params(kwargs...);
20 
21  std::cout << "foo:\n--------"
22  << "\na: " << a
23  << "\nb: " << b
24  // We can attempt to retrieve a key while providing a default fallback value.
25  // If c_key is in kwargs then this will return the value associated with
26  // that key, and will have the correct type. Note that the type of the default
27  // parameter in this case is const char*.
28  << "\nc: " << kw::Get(params,c_key,"null");
29  // We can also do stuff conditionally based on whether or not arg exists in
30  // the param pack. We still need to provide a default value, since we need to
31  // know the return type of the Get function when the key is not in kwargs.
32  // In this case, the default value wont ever be used at runtime.
34  std::cout << "\nd: " << kw::Get(params,d_key,0);
35  }
36 
37  std::cout << "\n\n";
38 }
39 
40 int main( int argc, char** argv )
41 {
42  foo(1, 2);
43  foo(1, 2, c_key=3);
44  foo(1, 2, c_key=3, d_key=4);
45  foo(1, 2, d_key=4);
46 }
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
Definition: kwargs.h:51
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...
Definition: kwargs.h:84
template meta-function contains a static boolean variable 'result' which is true if Tag is in the lis...
Definition: kwargs.h:65
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Definition: kwargs.h:73

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

1 #include <iostream>
2 #include "kwargs/kwargs.h"
3 
4 // Tags are just numeric constants that allow us to gerate a unique type for
5 // all of our keys. We can use enums, as in the previous example, or we can
6 // can use straight up literals. Though the enums are a lot more readable.
7 kw::Key<1> c_key;
8 
9 // We can utilize namespaces to keep our tag names from clobbering each other.
10 namespace test {
11 
12 enum Tags {
13  c_tag,
14  d_tag
15 };
16 
17 kw::Key<c_tag> c_key;
18 kw::Key<d_tag> d_key;
19 
20 } // namespace test
21 
22 template <typename... Args>
23 void foo(int a, int b, Args... kwargs) {
24  kw::ParamPack<Args...> params(kwargs...);
25 
26  std::cout << "foo:\n-------"
27  << "\na: " << a
28  << "\nb: " << b
29  // If we want to retrieve a parameter by it's tag value, and not
30  // it's key name, we can do that to, but this is pretty low-level.
31  << "\nc: " << kw::Get<0>(params,"null")
32  // Generally, using the key name is more readable.
33  << "\nd: " << kw::Get(params,test::d_key,"null")
34  << "\n\n";
35 }
36 
38 int main( int argc, char** argv )
39 {
40  foo(1, 2);
41  // foo() unpacks paremeter tagged with "0" so we can use any kw::Key<0> to
42  // assign it.
43  foo(1, 2, kw::Key<0>() = 3);
44  // Generally speaking, using a declared Key is more readable though, and
45  // more in the spirit of python-like kwargs.
46  foo(1, 2, test::c_key = 3);
47  // Unfortunately this opens up the avenue for abuse by using keys other than
48  // the intended key. For instance we can use c_key from the root namespace,
49  // which in fact packs the parameter with Key<1>, not Key<0> which is what
50  // would be packed with test::c_key... So we need to be careful and use
51  // the right keys.
52  foo(1, 2, c_key = 3);
53 }
54 
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
Definition: kwargs.h:51
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...
Definition: kwargs.h:84
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Definition: kwargs.h:73

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

1 #include <iostream>
2 #include "kwargs/kwargs.h"
3 
4 enum Keys {
5  c_tag,
6  d_tag
7 };
8 
9 kw::Key<c_tag> c_key;
10 kw::Key<d_tag> d_key;
11 
12 // This is a dummy class which just prints to cout whenever it is c'tor-ed or
13 // d'tor-ed to demonstrate the amount of copying that may happen
14 class Test {
15  public:
16  Test(const Test& other) { Construct(); }
17  Test() { Construct(); }
18 
19  ~Test() {
20  std::cout << "Destroyed object " << obj_id_ << "\n";
21  }
22 
23  int GetId() const { return obj_id_; }
24 
25  private:
26  void Construct() {
27  obj_id_ = n_objs_++;
28  std::cout << "Created object " << obj_id_ << "\n";
29  }
30  int obj_id_;
31  static int n_objs_;
32 };
33 
34 int Test::n_objs_ = 0;
35 
36 // when we print a test object, print it's ID
37 std::ostream& operator<<( std::ostream& out, const Test& test ){
38  out << test.GetId();
39  return out;
40 }
41 
42 // when we print a test object pointer, print the object's id
43 std::ostream& operator<<( std::ostream& out, const Test* test ){
44  out << "*" << test->GetId();
45  return out;
46 }
47 
48 template <typename... Args>
49 void foo(int a, int b, Args... kwargs) {
50  kw::ParamPack<Args...> params(kwargs...);
51 
52  std::cout << "foo:\n----"
53  << "\na: " << a
54  << "\nb: " << b
55  << "\ne: " << kw::Get(params,c_key,"null")
56  << "\nf: " << kw::Get(params,d_key,"null")
57  << "\n\n";
58 }
59 
60 int main( int argc, char** argv )
61 {
62  // Note that 5 test objects are created. The object that shows up in foo()
63  // is in fact the 4th copy of the original object!
64  foo(1, 2, c_key=Test());
65 
66  // We can avoid unnecessary copies by forcing the parameter to be passed
67  // by const ref. In this case, only one Test object is actually made.
68  foo(1, 2, d_key=kw::ConstRef(Test()));
69 
70  Test test_obj;
71  // We can use ConstRef on non-temporaries too. No additional object's are
72  // created, and foo() see's a reference to test_obj itself.
73  foo(1, 2, d_key=kw::ConstRef(test_obj));
74 
75  // For non-temporaries, we can use non-const ref as well. Again no additional
76  // object's are created, and foo() see's a reference to test_obj itself.
77  foo(1, 2, d_key=kw::Ref(test_obj));
78 
79  // Though, in general, if foo() needs to modify the object it may be better
80  // to pass a pointer. In this case foo() get's a non-const pointer to
81  // test_obj.
82  foo(1, 2, d_key=&test_obj);
83 }
RefWrap< T > Ref(T &v)
forces an argument to be passed by reference
Definition: kwargs.h:42
assignment operator sentinal used as a key for key-values pairs in the kwargs parameter pack ...
Definition: kwargs.h:51
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...
Definition: kwargs.h:84
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
Definition: kwargs.h:46
BinaryKey other(const BinaryKey &key)
Definition: BinaryKey.h:44
tooling for python-like kwargs in c++
storage for kwargs parameter pack
Definition: kwargs.h:73

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