Generalizing Unordered Named Arguments
Today’s guest post is written by Till Heinzel. Till is a physicist-turned-software developer at Luxion Aps in Denmark, who is very interested in expressive C++ and the growth of the language in a more expressive direction. Till can be found online on LinkedIn.
First off, I would like to thank Jonathan for creating FluentCpp and allowing me to contribute with this post.
One of the more subtle effects of using strong types for function-arguments is the fact that each argument is guaranteed to be of unique type. We can exploit that fact to create interfaces for functions that take the arguments in any order, and use some metaprogramming to put the arguments in their correct place, as Jonathan explores in his post on Named Arguments, upon which this post builds.
I was struck by Jonathan’s post as I tried to implement something similar a few years back when I was implementing a physics-library that contained some optimization algorithms. The algorithms had a lot of places where we wanted users to be able to adjust the behaviour (e.g. outputs from the algorithm, specific linesearches, stopping conditions, etc.), preferably by letting them inject their own code (security was not an issue).
Often, the injected parts would be very simple, so we decided to use a kind of policy-pattern, where users could pass callables to the algorithm, which would then call them at specific points during its execution. See this file for an example, around line 145.This lead to a lot of arguments for this function.
Worse, there was no sensible order to the arguments, and often we wanted some of them to be defaulted. While we could have used a struct and set its fields, this would have made the API harder for physicists, to whom that approach would not be intuitive.
So I decided to build a rather complex mechanism with named arguments in any, and to allow for defaults as well. So in a way the following is a refinement both of Jonathan’s approach and my own previous work.
Note: While I don’t think Named Arguments and unordered interfaces should be used indiscriminately, there are some cases where they can make a complex part of an API less so, at the expense of a bit more complex machinery for the developers.
Recap
In Jonathan post on Named Arguments, he arrives at the following:
// displayCoolName.hpp void displayCoolNameImpl(FirstName const& theFirstName, LastName const& theLastName); template<typename Arg0, typename Arg1> void displayCoolName(Arg0&& arg0, Arg1&& arg1) { displayCoolNameImpl(pick<FirstName>(arg0, arg1), pick<LastName>(arg0, arg1)); } // displayCoolName.cpp void displayCoolNameImpl(FirstName const& theFirstName, LastName const& theLastName) { std::cout << "My name is " << theLastName.get() << ", " << theFirstName.get() << ' ' << theLastName.get() << '.' << '\n'; }
Note: This works also without the named argument-syntax that is the main topic of that post. This is pretty cool! displayCoolName
can now be called in any order we want, just by labelling our arguments at call-site. While this is not useful in all contexts, there are corner-cases where this can really improve an API. Let’s see if we can generalize the approach a little. What we would like is to create a generic component that allows us to easily reproduce this pattern with
- different names,
- different impl-functions,
- and different parameters to be picked.
… without making the use of the component or the call to the resulting function more complex. That is a pretty tall order, and will require some atypical approaches.
Some preparation
First, let’s simplify things a little bit by assuming that NamedTypes are cheap to copy. As they typically wrap either a built-in type or a (const) reference to something more complex, I think this is reasonable. It removes the need to consider everything in terms of references and using forwarding references etc.
A first approach
Different impl-functions and parameters could be achieved by e.g. passing a functor and a typelist:
// displayCoolName.hpp template<typename... Args> void genericPicker(F f, TypeList<PickArgs>, Args... args) { auto tup = std::make_tuple(args...); f(std::get<PickArgs>(tup)...); } template<typename... Args> void displayCoolName(Args... args) { auto coolNameFunctor = [](FirstName firstName, LastName lastName) { displayCoolNameImpl(firstName, lastName); } genericPicker(coolNameFunctor, TypeList<FirstName, LastName>(), args...) }
However, this is definitely harder to use. It also doesn’t solve 1: we still have to define the template for each function we want to use the pattern with. Back to the drawing board.
Thinking outside the Box
The first requirement is really quite hard – how do you create a function that can have different names? My solution to this issue uses the fact that there is a second way we can create the syntax of a global function: a callable global variable. I saw that approach when looking at the code for boost::hana, where it is used to e.g. implement if. We can rewrite our earlier approach to
// UnorderedCallable.hpp template<class Function, class... OrderedParameters> class UnorderedCallable { public: constexpr UnorderedCallable(F f): f_(f) {} template<class... CallParameters> void operator() (CallParameters... Ts) const { auto tup = std::make_tuple(args...); f(std::get<PickArgs>(tup)...); } private: Function f_; }; // displayCoolName.hpp struct DisplayCoolNameImpl { void operator() (FirstName theFirstName, LastName theLastName); }; constexpr UnorderedCallable<DisplayCoolNameImpl, FirstName, LastName> displayCoolName;
Now we’re talking! This is definitely a reusable piece of code. However, we are still declaring the interface of the impl-function twice: once when we declare operator(), and once when we pass the argument-types to the UnorderedCallable
template. That is repeat work, and a potential source of errors. It can be solved by moving the declaration of the impl-function into UnorderedCallable
, and explicitly specializing the method:
// UnorderedCallable.hpp template<class FunctionID, class... OrderedParameters> class UnorderedCallable { public: constexpr UnorderedCallable(F f): f_(f) {} void impl(OrderedParameters... params) const ; template<class... CallParameters> void operator() (CallParameters... Ts) const { auto callParamTup = std::make_tuple(Ts...); impl( std::get<OrderedParameters>(callParamTup )...); } }; // displayCoolName.hpp using DisplayCoolName = UnorderedCallable<struct DisplayCoolNameID, FirstName, LastName> constexpr DisplayCoolName displayCoolName;
Almost there! The header and source look very close to those of a normal function.
Finishing off
We can do two more cheap improvements:
- Allow return values
- remove the named types from the impl-function by calling
.get()
in the template
With this, the final version is:
// UnorderedCallable.hpp template<class, class F> // we only use the partial specialization where F has the form Ret(Params) class UnorderedCallable{ static_assert(std::integral_constant<F>(false), "second template parameter must be of function type: Ret(Params)") } template<class FunctionID, class Retval, class... OrderedParameters> class UnorderedCallable<FunctionID, Ret(OrderedParameters)> { public: constexpr UnorderedCallable(F f): f_(f) {} Ret impl(typename OrderedParameters::type... params) const ; template<class... CallParameters> auto operator() (CallParameters... Ts) const { auto callParamTup = std::make_tuple(Ts...); return impl( std::get<OrderedParameters>(callParamTup ).get()...); } }; // displayCoolName.hpp using FirstName = NamedType<std::string const&, struct FirstNameID>; using LastName = NamedType<std::string const&, struct LastNameID>; using DisplayCoolName = UnorderedCallable<struct DisplayCoolNameID, void(FirstName, LastName)> constexpr DisplayCoolName displayCoolName; // displayCoolName.cpp void DisplayCoolName::impl(std::string const& theFirstName, std::string const& theLastName) { std::cout << "My name is " << theLastName << ", " << theFirstName << ' ' << theLastName << '.' << '\n'; }
Using NamedType
of references together with the function form for the template parameters to UnorderedCallable
makes the declaration look like simple pseudocode. Using the underlying type directly in the implementation-function makes the implementations’ bodies look exactly like normal function definitions, without losing the usefulness of NamedTypes
. The only danger I see is that when you want to swap the order of the two arguments, the compiler would not help you. However, you don’t ever need to do that because you can pass the arguments in in any order anyway, and it makes more complex function definitions way easier to read without all the .get()
calls. Note that this may require some slight adjustments to NamedType
itself. The complete version of this approach is in the repo, which also has support for default values.
Related articles
Don't want to miss out ? Follow:   Share this post!