Named Arguments in C++
Ah, named arguments!
If the term “feature envy” wasn’t already used to designate a refactoring, we would have employed it to talk about named arguments: it’s a feature that languages that don’t have it envy to the languages that do.
Named arguments consist in specifying at call site the parameter names of the arguments passed. For instance, consider this function:
void displayCoolName(std::string const& firstName, std::string const& lastName) { std::cout << "My name is " << lastName << ", " << firstName << ' ' << lastName << '.'; }
The call site of that function looks like this:
displayCoolName("James", "Bond");
(hey, want to try it out with your own name to see how cool your name sounds?)
With named arguments, the call site would rather look like that:
displayCoolName(firstName = "James", lastName = "Bond");
It has the advantage of being more explicit so that you don’t mix up the order of the parameters. Also, a reader of the call site doesn’t have to go check the function’s prototype to understand the meaning of the function’s parameters.
Some languages have this. Objective-C has it, Python has something not far, hell even C99 has something resembling it for initiating structures.
And us in C++? We stand here, envying the feature.
Envy no more, here is a technique to implement named arguments in C++.
I will present this in 3 steps:
- step 1: strong types to differentiate parameters,
- step 2: a trick to get the right syntax,
- step 3: going further: passing the parameters in any order.
I want to thank Reddit user /u/matthieum from which I got largely inspired when he commented on the Reddit thread of Strong types for strong interfaces.
Step 1: Strong types to differentiate parameters
If you are a regular reader of Fluent C++, you probably have already encountered strong types.
Strong types consist in replacing a type with another one that adds meaning through its name. In our above example we could create two strong types, FirstName
and LastName
that both wrap a std::string
to pin a specific meaning (like representing a first name, or a last name) over it.
For this we will use the NamedType
library, of which you can get a overview on its GitHub page or in Strong types for strong interfaces, if you’re not familiar with it.
using FirstName = NamedType<std::string, struct FirstNameTag>; using LastName = NamedType<std::string, struct LastNameTag>;
These are two different types. They both wrap a std::string
that they expose through their .get()
method.
Let’s replace the naked std::string
s in our displayCoolName
function with those strong types:
void displayCoolName(FirstName const& firstName, LastName const& lastName) { std::cout << "My name is " << lastName.get() << ", " << firstName.get() << ' ' << lastName.get() << '.'; }
Now here is what a call site looks like:
displayCoolName(FirstName("James"), LastName("Bond"));
That can play the role of named arguments, and it would already be reasonable to stop here.
But let’s wrap around the C++ syntax to get to those oh-so-enviable named arguments, with the equal sign and all.
Step 2: A trick to get the right syntax
We would like to be able to write a call site like this one:
displayCoolName(firstName = "James", lastName = "Bond");
Let’s reason about this syntax: we need the first argument to be of type FirstName
. So we need to define an object firstName
that has an operator=
that takes a std::string
(or something convertible to it) and that returns a FirstName
.
Let’s implement the type of this object firstName
. We call this type argument
. Since argument
must know FirstName
, which is a template class, I think the most convenient is to implement argument
as a nested class inside the class FirstName
.
FirstName
is an alias of NamedType
, so let’s add argument
inside NamedType
:
template< /* the template args of NamedType */ > class NamedType { public: // ... struct argument { template<typename UnderlyingType> NamedType operator=(UnderlyingType&& value) const { return NamedType(std::forward<UnderlyingType>(value)); } }; };
We can now create the firstName
and lastName
helpers to accompany our function:
static const FirstName::argument firstName; static const LastName::argument lastName; void displayCoolName(FirstName const& theFirstName, LastName const& theLastName) { std::cout << "My name is " << theLastName.get() << ", " << theFirstName.get() << ' ' << theLastName.get() << '.'; }
And now the call site of displayCoolName
looks at last like this:
displayCoolName(firstName = "James", lastName = "Bond");
Yay, named arguments!
The NamedType library now has this feature available.
As a side note, since the firstName
and lastName
helpers are not supposed to be passed to a function, let’s delete the default-generated move and copy methods:
struct argument { template<typename UnderlyingType> NamedType operator=(UnderlyingType&& value) const { return NamedType(std::forward<UnderlyingType>(value)); } argument() = default; argument(argument const&) = delete; argument(argument&&) = delete; argument& operator=(argument const&) = delete; argument& operator=(argument&&) = delete; };
Step 3: Going further: passing the parameters in any order
Since we indicate which argument corresponds to what parameter, do we really need a fixed order of arguments?
Indeed, it would be nice if any given call site had the choice to write either this:
displayCoolName(firstName = "James", lastName = "Bond");
or that:
displayCoolName(lastName = "Bond", firstName = "James");
and that it would have the same effect.
We’re going to see a way to implement this. However, I don’t think it is production-ready because of some readability drawbacks that we will see.
So from his point on we’re threading into the exploratory, and of course your feedback will be welcome.
Since we don’t know the types of the first and second parameter (either one could be FirstName
or LastName
), we are going to turn our function into a template function:
template<typename Arg0, typename Arg1> void displayCoolName(Arg0&& arg0, Arg1&& arg1) { ...
Now we need to retrieve a FirstName
and a LastName
from those arguments.
Picking an object of a certain type amongst several objects of different types sounds familiar: we can use std::get
on a std::tuple
like when we used strong types to return multiple values.
But we don’t have a std::tuple
, we only have function arguments. Fortunately, there is nothing easier than packing function arguments into a std::tuple
, thanks to the std::make_tuple
function. The resulting code to pick a type looks like this:
template<typename TypeToPick, typename... Types> TypeToPick pick(Types&&... args) { return std::get<TypeToPick>(std::make_tuple(std::forward<Types>(args)...)); }
Let’s use this to retrieve our FirstName
and LastName
from the arguments:
template<typename Arg0, typename Arg1> void displayCoolName(Arg0&& arg0, Arg1&& arg1) { auto theFirstName = pick<FirstName>(arg0, arg1); auto theLastName = pick<LastName>(arg0, arg1); std::cout << "My name is " << theLastName.get() << ", " << theFirstName.get() << ' ' << theLastName.get() << '.' << '\n'; }
Now we can call either:
displayCoolName(firstName = "James", lastName = "Bond");
or:
displayCoolName(lastName = "Bond", firstName = "James");
And in both cases we get:
My name is Bond, James Bond.
One of the drawbacks that I see with this latest technique is that it converts our function into a template. So it needs to go to a header file (unless we do explicit instantiation of all the permutations of the arguments).
To mitigate this, we could extract a thin layer that picks the arguments and forwards them the the function as it was before:
// .hpp file 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)); } // .cpp file void displayCoolNameImpl(FirstName const& theFirstName, LastName const& theLastName) { std::cout << "My name is " << theLastName.get() << ", " << theFirstName.get() << ' ' << theLastName.get() << '.' << '\n'; }
Another drawback is that the names of the parameters in the prototype lose all their meaning (“Arg0″…).
If you see other drawbacks, or if you see how to improve this technique to be able to pass function argument in any order, share it in a comment!
Don't want to miss out ? Follow:   Share this post!