[Note: thanks to Davide Di Gennaro for having reviewed this post and for having suggested some improvements. A paragraph has been completely written by Davide.]
[From Wikipedia] In programming, named parameters refer to a computer language’s support for function calls that clearly state the name of each parameter within the function call itself. A function call using named parameters differs from a regular function call in that the values are passed by associating each one with a parameter name, instead of providing an ordered list of values.
Compare with a traditional positional function call:
createArray(10, 20); // what does this mean, precisely? createArray(length=10, capacity=20); // oh, I see... createArray(capacity=20, length=10); // same as previous
A typical example:
// some pseudo-language window = new Window { xPosition = 10, yPosition = 20, width = 100, height = 50 };
This approach is especially useful if a function takes a large number of optional parameters, and users are usually going to accept the default for most of them.
Several languages support named parameters (e.g. C#, Objective-C, …). C++ does not. In this post, I’m going to explore some of the classical ways to emulate named parameters in C++ as well as mention new approaches.
Comments
Only to mention, the most trivial way to emulate named parameters is through comments 🙂
Window window { 10, // xPosition 20, // yPosition 100, // width 50 // height };
This approach is very popular among windows developers, as MSDN pubishes Windows API usage examples with such comments.
Named Parameter Idiom
Imported from Java programming style is the Named parameter idiom (and this is just another post about). The idea is simple: create a proxy class which houses all the parameters. Optional ones are settable via a fluent interface (e.g. by using method chaining):
// 1 File f { OpenFile{"path"} // this is mandatory .readonly() .createIfNotExist() . ... }; // 2 classical version (not compliant with the auto-everything syntax) File f = OpenFile { ... } .readonly() .createIfNotExist() ... ; // 3 for auto-everything syntax just add a layer (I prefer 1) auto f = CreateFile ( OpenFile("path") .readonly() .createIfNotExists() . ... ));
OpenFile is a sort of parameter object and File’s constructor accepts an OpenFile instance. Some authors (for instance, here) argue that OpenFile should have only private members and declare File as friend. This makes sense if you want to add more complex logic to set your parameters via the fluent interface. If you merely want to set plain parameters then a public struct suffices.
In this approach:
- mandatory parameters are still positional (as the constructor of OpenFile is a regular function call),
- optional parameters must be copy/move-assignable (basically, settable) – possible runtime penalty,
- you need to write an extra class (the proxy).
I – almost – never found usages in critical code.
A final note: the fluent interface can be polymorphic as I wrote more than two years ago.
Parameter Pack Idiom
Similar to the one above it’s the parameter pack idiom – from Davide Di Gennaro’s Advanced C++ Metaprogramming – a technique using proxy objects to set parameters via assignment operator (=), resulting in a sweet syntactic sugar:
MyFunction(begin(v), end(v), where[logger=clog][comparator=greater<int>()]);
The actors involved here are:
- logger and comparator are global constants; the assignment operator just returns a wrapped copy of the assigned value,
- where is a global constant of type “parameter pack”, whose operator[] just returns a new proxy that replaces one of its members with the new argument.
In symbols:
where = {a, b, c } where[logger = x] → { a,b,c }[ argument<0>(x) ] → {x,b,c}
Sketching an implementation, just to give you an idea:
// argument template <size_t CODE, typename T = void> struct argument { T arg; argument(const T& that) : arg(that) { } }; // void argument - just to use operator= template <size_t CODE> struct argument<CODE, void> { argument(int = 0) { } template <typename T> argument<CODE, T> operator=(const T& that) const { return that; } argument<CODE, std::ostream&> operator=(std::ostream& that) const { return that; } }; // argument pack (storing the values) template <typename T1, typename T2, typename T3> struct argument_pack { T1 first; T2 second; T3 third; argument_pack(int = 0) { } argument_pack(T1 a1, T2 a2, T3 a3) : first(a1), second(a2), third(a3) { } template <typename T> argument_pack<T, T2, T3> operator[](const argument<0, T>& x) const { return argument_pack<T, T2, T3>(x.arg, second, third); } template <typename T> argument_pack<T1, T, T3> operator[](const argument<1, T>& x) const { return argument_pack<T1, T, T3>(first, x.arg, third); } template <typename T> argument_pack<T1, T2, T> operator[](const argument<2, T>& x) const { return argument_pack<T1, T2, T>(first, second, x.arg); } }; enum { LESS, LOGGER }; const argument<LESS> comparator = 0; const argument<LOGGER> logger = 0; typedef argument_pack<basic_comparator, less<int>, std::ostream> pack_t; static const pack_t where(basic_comparator(), less<int>(), std::cout);
For the complete code, please refer to Davide’s book.
While this technique may look interesting, in practice it’s hard to generalize. In the book, in fact, it’s included as an example of “chaining” multiple calls to operator[].
Tagging
Andrzej Krzemieński published an interesting post Intuitive interface, where he mentions an alternative approach.
Named parameters are introduced by companion tags (empty structs used just to select different overloads of the same function). Notable examples of tags are from the STL:
std::function<void()> f{std::allocator_arg, a}; // treats a as an allocator instead of a callble object std::unique_lock<std::mutex> l{m, std::defer_lock}; // don't lock now
Andrzej proposes tags to improve readability:
// not real STL std::vector<int> v1(std::with_size, 10, std::with_value, 6);
I like his approach, but – as it stands – you possibly need to create lots of overloads and you cannot choose the order of the parameters. However, there are no requirements on copy-assignment, default values and forwarding is also clear. From the article: “However, tags are not an ideal solution, because they pollute the namespace scope, while being only useful inside function (constructor) call.”
Additionally (from the comments to the article) a reader proposes a slightly different idea that uses a proxy:
std::vector<int> v1(std::with_size(10), std::with_value(6));
Boost
Boost has the Parameter Library.
It’s possibly the most complete option if you really need named parameters in C++. An example:
// class code #include <boost/parameter/name.hpp> #include <boost/parameter/preprocessor.hpp> #include <string> BOOST_PARAMETER_NAME(foo) BOOST_PARAMETER_NAME(bar) BOOST_PARAMETER_NAME(baz) BOOST_PARAMETER_NAME(bonk) BOOST_PARAMETER_FUNCTION( (int), // the return type of the function, the parentheses are required. function_with_named_parameters, // the name of the function. tag, // part of the deep magic. If you use BOOST_PARAMETER_NAME you need to put "tag" here. (required // names and types of all required parameters, parentheses are required. (foo, (int)) (bar, (float)) ) (optional // names, types, and default values of all optional parameters. (baz, (bool) , false) (bonk, (std::string), "default value") ) ) { if (baz && (bar > 1.0)) return foo; return bonk.size(); } //client code function_with_named_parameters(1, 10.0); function_with_named_parameters(7, _bar = 3.14); function_with_named_parameters( _bar = 0.0, _foo = 42); function_with_named_parameters( _bar = 2.5, _bonk= "Hello", _foo = 9); function_with_named_parameters(9, 2.5, true, "Hello");
Modern named parameters
Modern C++ opened some new doors. Can the new language features lead to slimmer implementations of named parameters?
Lambdas
Method chaining is verbose. I don’t like adding all the functions returning the object itself. What about defining just a struct and assign all the members through a lambda?
struct FileRecipe { string Path; // mandatory bool ReadOnly = true; // optional bool CreateIfNotExist = false; // optional // ... }; class File { File(string _path, bool _readOnly, bool _createIfNotexist) : path(move(_path)), readOnly(_readOnly), createIfNotExist(_createIfNotExist) {} private: string path; bool readOnly; bool createIfNotExist; }; auto file = CreateFile( "path", [](auto& r) { // sort of factory r.CreateIfNotExist = true; });
You still have to provide a parameter object but this approach scales quite better than the classical named parameter idiom in which even chaining functions have to be written.
A variant consists in making File constructible from a FileRecipe (like the named parameter idiom).
How to improve the fluency of mandatory parameters? Let’s mix this approach with tags:
auto file = CreateFile( _path, "path", [](auto& r) { r.CreateIfNotExist = true; });
But they are still positional. If you rest easy with a runtime error if a mandatory parameter is missing then use an optional type and check it at runtime.
CreateFile is trivial and left to the reader.
I’ve recently used this approach to configure test recipes and mocks. For example, I needed to create tests of a trivial dice game. Every game had a configuration and tests used to look like:
TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { GameConfiguration gameConfig { 5u, 6, 2u }; }
By using the approach above we could have:
TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { auto gameConfig = CreateGameConfig( [](auto& r) { r.NumberOfDice = 5u; r.MaxDiceValue = 6; r.NumberOfTurns = 2u; }); }
Diehards may suggest a macro to reduce verbosity:
TEST_F(SomeDiceGameConfig, JustTwoTurnsGame) { auto gameConfig = CREATE_CONFIG( r.NumberOfDice = 5u; r.MaxDiceValue = 6; r.NumberOfTurns = 2u; ); }
Exploiting Variadics
Variadics can improve techniques I described above. What about Andrej’s tags approach? Tags could be preferred over the lambda + parameter object because you don’t have to create another object, you don’t have problems with settability and you consider all the parameters the same (e.g. by using the lambda approach you have to treat mandatory parameters differently). But I think tags would be better, if I could:
- define only one overload of my constructor (or function),
- decide the order of the parameters (pairs tag-value),
- the two above + having optional and mandatory parameters.
Something simple like:
File f { _readonly, true, _path, "some path" };
or (my preference):
File f { by_name, Args&&... args) {}
My idea is: I just want to use variadics to let the user decide the order and let her omit optional parameters.
Imagine two constructors:
File(string path, bool readonly, bool createIfNotExist) {} // all mandatory template<typename... Args> File(by_name_t, Args&&... args) {}
A instance of File can be created by using both. If you use the variadic one then I’ll look for all parameters in the pack and delegates the other constructor to really make the instance. Search is (at compile-time) linear over the pack that contains parameters in the order chosen by the caller.
[Note: my implementation is just a proof of concept I did more than one year ago (I only added decltype(auto) somewhere). It could be done better and better.]
Here is how the class designer may look at her class:
File(string path, bool readonly, bool createIfNotExists /*...*/) : _path (move(path)), _createIfNotExist(createIfNotExist), _readonly(readonly) // ,etc... { } template<typename Args...> File(named_tag, Args&&... args) : File{ REQUIRED(path), OPTIONAL(read, false) // , etc... } // delegating { }
Prior to show you a working code, it’s clear we can apply the same idea to proxies and obtain:
auto f = File { by_name, readonly=true, path="path" };
The real difference here is about forwarding: with proxies, we benefit from the syntax sugar (the operator=) but now we have to store the values and forward them (not ideal for non-movable/copyable types – and other problems could arise).
Here you can play with the code (and here is the same file on Gist). I first started with the tag version and then I tried with proxies. For this reason there are two versions: the former works with tags ([tag, value]…) and the latter with proxies ( [tag=value]…). Some code could (and should) be refactored.
You’ll find two sections called “PACK UTILS” (two versions: tag and proxy). These contain code I wanted to play originally (e.g. playing with variadics). I also think these kind of operations can be done by using std::forward_as_tuple and then by exploiting tuple’s utilities.
Another part of the code contains macros to retrieve parameters and to generate tags.
The final section is a full example.
Here is what a class looks like:
class window { public: // classic constructor window( string pTitle, int pH, int pW, int pPosx, int pPosy, int& pHandle) : title(move(pTitle)), h(pH), w(pW), posx(pPosx), posy(pPosy), handle(pHandle) { } // constructor using proxies (e.g. _title = "title") template<typename... pack> window(use_named_t, pack&&... _pack) : window { REQUIRED_NAME(title), // required OPTIONAL_NAME(h, 100), // optional OPTIONAL_NAME(w, 400), // optional OPTIONAL_NAME(posx, 0), // optional OPTIONAL_NAME(posy, 0), // optional REQUIRED_NAME(handle) } // required { } // constructor using tags (e.g. __title, "title") template<typename... pack> window(use_tags_t, pack&&... _pack) : window { REQUIRED_TAG(title), // required OPTIONAL_TAG(h, 100), // optional OPTIONAL_TAG(w, 400), // optional OPTIONAL_TAG(posx, 0), // optional OPTIONAL_TAG(posy, 0), // optional REQUIRED_TAG(handle) } // required { } private: string title; int h, w; int posx, posy; int& handle; };
You see, both named and tag constructors always delegate the real constructor to perform initialization.
The following code fragment shows how the caller uses the contraption:
int i=5; // tags version window w1 {use_tags, __title, "Title", __h, 10, __w, 100, __handle, i}; cout << w1 << endl; // proxies version window w2 {use_named, _h = 10, _title = "Title", _handle = i, _w = 100}; cout << w2 << endl; // classic version window w3 {"Title", 10, 400, 0, 0, i}; cout << w3 << endl;
by_name here is called use_named, but the meaning is the same.
Pros:
- mandatory and optional parameters are uniform (named or tagged)
- order is not defined a priori
- tag approach has no forwarding issues
Cons:
- compile-time errors could be hard to understand (static_assert helps a bit)
- available parameters should be documented
- pollution of the namespace scope still remains
- default-values are always evaluated (some improvements for laziness are possible)
- proxy approach is not ideal for forwarding.
A note about the first trouble: Clang is a gentleman and it complains politely. For instance, suppose I forget a title for my window. Here is the output:
main.cpp:28:2: error: static_assert failed "Required parameter" static_assert(pos >= 0, "Required parameter"); ^ ~~~~~~~~
main.cpp:217:14: note: in instantiation of template class 'get_at<-1, 0>' requested here : window { REQUIRED_NAME(title), ^
This way you know precisely where you miss a required parameter. This could be improved.
A minimalistic approach using std::tuple
[Note: completely by Davide Di Gennaro]
We can exploit some of the power of std::tuple to write an extremely compact and portable implementation. We will stick to some simple principles:
- the parameter pack will be a special tuple, where a “tag type” is immediately followed by its value (so the type would be something like std::tuple<age_tag, int, name_tag, string, … >)
- the standard library already has utility functions to forward / concatenate objects and tuples that guarantee optimal performance and correctness in move semantics
- we will use a macro to introduce global constants that represent a tag
- the syntax for constructing a parameter pack will be (tag1=value1)+(tag2=value2)+…
- the client will take a parameter pack as a reference to template type, e.g.
template <typename pack_t> void MyFunction([whatever], T& parameter_pack) // or const T&, T&&, etc.
- With a function call, the client will extract a value from the pack and (say) move it into a local variable.
Ideally, here’s how the code will look like:
namespace tag { CREATE_TAG(age, int); CREATE_TAG(name, std::string); } template <typename pack_t> void MyFunction(T& parameter_pack) { int myage; std::string myname; bool b1 = extract_from_pack(tag::name, myname, parameter_pack); bool b2 = extract_from_pack(tag::age, myage, parameter_pack); assert(b1 && myname == "John"); assert(b2 && myage == 18); } int main() { auto pack = (tag::age=18)+(tag::name="John"); MyFunction(pack); }
Here is how the implementation may look like. We will omit most of the potential optimizations for sake of clarity (and they are probably unnecessary).
First, the macro:
#include <tuple> #include <utility> template <typename T> struct parameter {}; #define CREATE_TAG(name, TYPE) \ \ struct name##_t \ { \ std::tuple<parameter<name##_t>, TYPE> operator=(TYPE&& x) const \ { return std::forward_as_tuple(parameter<name##_t>(), x); } \ \ name##_t(int) {} \ }; \ \ const name##_t name = 0
The expansion of CREATE_TAG(age, int); creates a class and a global object. Note that this will work if positioned inside a namespace.
struct age_t { std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::forward_as_tuple(parameter<age_t>(), x); } age_t(int) {} }; const age_t age = 0;
Conceptually the assignment
age = 18
Translates into something similar to:
make_tuple(parameter<age_t>(), 18);
Observe that we wrote:
std::tuple<parameter<age_t>, int> operator=(int&& x) const
As written, we require an r-value on the right. First, this is an extra safety feature: to increase the readability of the code with parameter packs, you may want to assign constants, not variables (otherwise, renaming the variable would be sufficient). e.g.
int myage = 18; f(myage); // ok, clear g((...) + (age=18)); // ok, clear g((...) + (age=myage)); // compiler error, and redundant from a readability point of view
Second, we can exploit move semantics:
The difference between
std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::make_tuple(parameter<age_t>(), x); }
and
std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::forward_as_tuple(parameter<age_t>(), x); }
is very subtle. The latter returns std::tuple<…, int&&>, but since the return type is tuple<…, int> then tuple’s move constructor is invoked.
Alternatively we could write
std::tuple<parameter<age_t>, int> operator=(int&& x) const { return std::make_tuple(parameter<age_t>(), std::move(x)); }
Now, we add a suitable tuple-concatenation operator.
We informally agree that all tuples starting with parameter<T> have been generated by our code, so without any explicit validation, we just cat them:
template <typename TAG1, typename... P1, typename TAG2, typename... P2> std::tuple<parameter<TAG1>, P1..., parameter<TAG2>, P2...> operator+ (std::tuple<parameter<TAG1>, P1...>&& pack1, std::tuple<parameter<TAG2>, P2...>&& pack2) { return std::tuple_cat(pack1, pack2); }
Very simply, this function will do a simple pattern matching on two tuples: if they both look like:
tuple<parameter<tag>, type, [maybe something else]>
then they are joined together.
Finally, we publish a function to perform the extraction of an argument from the pack. Note that this function has move semantics (i.e. after a parameter is moved out of the pack).
template <typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack);
the effect of this function is:
if the “pack” contains parameter<TAG>, then var receives the value immediately following, and the function returns true. otherwise something bad happens (we can choose between: a compiler error, return false, throw exception, and a few more…)
To make this selection possible, actually the function will be:
template <typename ERR, typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack)
So we will invoke it as:
extract_from_pack< erorr_policy > (age, myage, mypack);
Due to variadic templates pattern matching, extract_from_pack knows that the pack has the form tuple<parameter<TAG1>, … > so it needs to examine recursively if TAG is equal to TAG1. We will do this dispatching the call to a class:
extract_from_pack< erorr_policy > (age, myage, mypack);
calls
extractor<0, erorr_policy >::extract (age, myage, mypack);
which in turn calls
extractor<0, erorr_policy >::extract (age, myage, std::get<0>(pack), mypack);
which has two overloads:
extract(TAG, … , TAG, …)
which succeeds, performs the assignment and returns true, or
extract(TAG, … , DIFFERENT_TAG, …)
which keeps on iterating, calling again
extractor<2, erorr_policy >::extract (age, myage, mypack);
when iteration is not possible, error_policy::err(…) is invoked.
template <size_t N, typename ERR> struct extractor { template <typename USERTAG, typename T, typename TAG, typename... P> static bool extract(USERTAG tag, T& var, std::tuple<parameter<TAG>, P...>&& pack) { return extract(tag, var, std::get<N>(pack), std::move(pack)); } template <typename USERTAG, typename T, typename TAG, typename... P> static bool extract(USERTAG tag, T& var, parameter<TAG> p0, std::tuple<P...>&& pack) { return extractor<(N+2 >= sizeof...(P)) ? size_t(-1) : N+2, ERR>::extract(tag, var, std::move(pack)); } template <typename USERTAG, typename T, typename... P> static bool extract(USERTAG tag, T& var, parameter<USERTAG>, std::tuple<P...>&& pack) { var = std::move(std::get<N+1>(pack)); return true; } }; template <typename ERR> struct extractor<size_t(-1), ERR> { template <typename TAG, typename T, typename DIFFERENT_TAG, typename... P> static bool extract(TAG tag, T& var, std::tuple<parameter<DIFFERENT_TAG>, P...>&& pack) { return ERR::err(tag); } }; template <typename ERR, typename TAG, typename T, typename... P, typename TAG1> bool extract_from_pack(TAG tag, T& var, std::tuple<parameter<TAG1>, P...>& pack) { return extractor<0, ERR>::extract(tag, var, std::move(pack)); }
Due to the flexible nature of parameter packs, the best error policy would be a plain “return false” (any stronger error would in fact make that parameter mandatory). So:
struct soft_error { template <typename T> static bool err(T) { return false; } };
However we are free to chose any of these:
struct hard_error { template <typename T> static bool err(T); // note that static_assert(false) here won’t work. can you guess why? }; struct throw_exception { template <typename T> static bool err(T) { throw T(); return false; } };
An additional improvement could be a redundancy check, that prevents code like (age=18)+(age=19).
The code for this is short, but it requires some subtle manipulation with variadic templates, so we leave it as an excercise.
Final notes
I have not discussed about runtime techniques, e.g.:
void MyFunction ( option_parser& pack ) { auto name = pack.require("name").as<string>(); auto age = pack.optional("age", []{ return 10; }).as<int>(); ... }
Whereas the opening techniques I have presented are fairly consolidated, last ideas are just work in progress and I’m still trying to understand if they make sense. The code I’ve shown is just a proof of concept and it does not claim to be optimal nor suitable for production. I wrote these lines more than one year ago, to practice with variadics and I finally found some time to summarize my ideas in a decent (I hope) post and share with you. If this will be helpful or inspiring to anyone, I’ll be really glad.
I found a recent proposal aiming to introduce named arguments in C++ here. Cool!
Anyhow I have a question for you: where would you have wanted to use named parameters in C++?
Nice post and really well described.
The idea of using named parameters is interesting, but how much this solution is scalable (eg., overloading of functions, class inheritance)?
I still believe that keeping the functions signature easy, then we can avoid those headaches.
Thanks a lot!
First of all, we proposed two proofs of concept. Regarding my approach with variadics, overloading is feasible (by using more metaprogramming) but I wouldn’t do that. I think it’s not worth the trouble because I consider named parameters useful when you need to fluently express a recipe. Some ingredients are optional – a default value will be provided automatically – and others are mandatory – you have to mention them.
Lambda approach (the one “replacing” the named parameter idiom) is enough scalable. Its main drawback (as the named parameter idiom) is maintaining a parallel structure (the recipe), but this is not so tremendous in lots of use-cases.
And as I suggested, maintaining a “normal” signature is better than having just a “named” one (consider the example with the Window class).
Yes indeed. I mean, the proof of concept is really cool for the final usage, but it’s hard to implement in a large scale project probably.
By the way, in your shoes, I would still consider to submit this proposal to the committee. Perhaps it could be reviewed and become part of the standard (not C++17, but maybe in future, who knows).
Anyway, nice blog and nice effort 😉
Thanks 🙂 I think this proposal http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n4172.htm is better! If really needed, named parameters should be supported at language-level!
Perhaps you should examine Ada for answers to your concerns. Ada has had named parameters in the language since it was first officially released to the public in 1983, and for a number of years prior to that while it was still under development. Subroutine overloading was also included and object-orientation was added in the 1995 release. It has continued to be developed over the years with the latest official update being in 2012. It has a very good reputation in large-scale systems, especially safety critical ones and its strong typing, with the help of named parameters helps to contribute to that without any developers having to implement the sort of nonsense above to try to replicate what should, by now, be a feature that’s inherent in the language.
I love the detailed analysis.
Personally, I dislike how much these options inflate the code. It becomes harder to read. Frankly, I’d rather see a long list of parameters and be forced to jump to the definition to see what they all are than have the definition all mucked up with custom enhancements to force the user to name their parameters.
I certainly endorse the proposal to added named parameter passing. Short of that, I think a configuration class/struct is the next best thing. Its constructor can give it default settings, and the user can simply change the ones he or she is interested in. Keep in mind too that C++11 introduced constexpr, which could arguably reduce hard-coded config object cost to zero.
Ugh. I can’t edit my post?
…proposal to _add_ named…
if I didn’t miss something, another c++11-enabled technique could be something like:
void f( T0 a0, … ); // some function
auto f_ = make_named_param_function( f, “a0″_p, … );
f_( “a0″_p = v0, … );
where “a0″_p is a ud string literal encoding its content at compile time ( yes, we can do that ) used to dispatch the argument to its matching position, and exposing an operator= the obvious way. In addition to decoupling the function from its named version ( declared locally, globally, whatever ), this would also have the advantage of generalizing the match mechanism ( say, “grouped assignment” like “(a0,a1)”_p = 0, runtime regex, etc.. etc… ).
BTW, I think it’s also worth noting that the need of named parameters can be just the symptom that the function is doing too much, and that its signature should be broken into smaller ( and more meaningful ) pieces, say, self-evident calls like do_this_or_that(this,that) should be preferred IMO …
just to add a more concrete example, and to show how this would compare from the fluentness pov:
// window.hpp
void create_window( std::string const& caption, pos_t const& position /*, …*/ );
// named/window.hpp
// …
constexpr auto create_window_with = make_named_param_function( create_window,
“caption”_p = “notitle”, “position”_p = pos_t::zero,
“popup”_defaults = make_defaults_pack( “caption”_p = “notitle” /*, …*/ )
/*,…*/ );
// user_code.cpp
//…
create_window_with( “caption”_p = “mytitle” );
create_window_with( “popup”_defaults );
// …
Hi there, thanks for commenting.
I don’t understand how you create different proxies according to the passed string, at compile-time. For instance: do “caption”_p and “position”_p generate different proxies at compile-time? It looks promising! Can you please show a complete and working implementation? Thanks!
Sorry, but (despite the cleverness of your solutions) this looks awful to me, especially the name parameter idiom (no wonder Java apps are so slow when you over design idioms and patterns). I would suggest you make a proposal for supporting this at a language level, rather then using strange hacks to fake a similar result. No offence intended.
Hi, here is a proposal (not mine): http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n4172.htm
Target of this post was to describe (classical and not) ways to support named parameters in C++, in particular what I found in codebases I have worked on in the last years. About the approaches using variadics and tuples I think it’s worth sharing because they are good exercises of metaprogramming 😉 They are not intended to be used in production!
Thanks for reading!
You are way way overcomplicating this. A solution already exists in C++, and even in C! Just make a struct called MyFnParams (or MyClassParams for a constructor) with all the necessary parameters. This uses simple core language features with no obscure notation, both at the point of declaring the function and the point of using it, so is easier to maintain than any cryptic tricks. It also splits the function call over several distinct statements (i.e. semicolon-terminated lines), which is what you want if it’s complicated enough that you need named parameters. Here’s how it looks in practice:
// definition in header
class MyClass {
public:
struct Params {
int foo;
std::string bar;
// Perhaps a lot more stuff
};
explicit MyClass(const Params& params);
// … other stuff
};
// use in client code
MyClass::Params myClassParams;
myClassParams.foo = someFn(zig);
MyClass obj(myClassParams);
Of course, this is really a variant of what you call the “Named Parameter Idiom”, but without parameter chaining. But parameter chaining seems to be extra work for the sake of more confusing syntax: a lose-lose situation. A couple of your objections to that idiom do hold for this too: there are some performance concerns (although I imagine a function with lots of parameters is rarely in a critical loop, and even if it were you could reuse the Params object and perhaps even get an improvement) and you lose the compile-time ability to enforce compulsory arguments (compile-time error finding is always nice, but in my experience it’s not a big loss here). The complaint about needing to write a class every time doesn’t hold here because there’s no need to add getters or setters or anything (even if you add a constructor to make sensible defaults), and this is barely more code than just putting the parameters straight in the function. Unlike your experience of the Named Parameter Idiom, I’ve seen this a lot in production code, and it works very well.
This approach was one of first I mentioned. it’s commonly called “parameter object” and I’ve been using it too.
Thanks for your comment.
A function object serves well:
// my function with named and positional parameters definition
struct myFunc_ {
bool namedParam1 {true};
bool namedParam2 {false};
int operator() (int positionalParam = 0) {
// implementation
}
};
// data related to using the function
std::vector namedParameterValues = {true, false};
std::vector positionalParameterValues = {0,1,2,3,4};
std::vector results;
// using the function
myFunc_ myFunc;
for (auto namedValue: namedParameterValues )
for (auto positionalValue: positionalParameterValues) {
myFunc.namedParam2 = namedValue;
results.push_back( myFunc(positionalValue) );
}
Hello
You may be interested by some of my work on the same subject : https://github.com/duckie/named_types
Regards
Thanks, I’ll check that out!
Hi
I like your proposition that uses lambda.
I had a moment of hesitation when I read “CreateFile is trivial and left to the reader” 🙂
I think it leads to a code that is quite short, although a bit convoluted (but not that much on the usage side).
struct foo_param {
int i = 0;
std::string folder { “/” };
};
void foo(const foo_param& param) {
std::cout << param.i << " " << param.folder;
}
template
auto foo_(paramModifierFunction && fun) {
foo_param p;
fun(p);
return foo(p);
}
void mycall() {
foo_([](auto & p) {
p.folder = “/usr/local/bin”;
});
}
Furthermore, I you are willing to abuse the preprocessor, you can make it even shorter.
First, define two macros :
#define make_lambda_caller(function_name, param_name, dst_function_name) \
template \
auto dst_function_name(ParamModifierFunction && paramModifierFunction) { \
param_name param; \
paramModifierFunction(param); \
return function_name(param); \
}
#define make_lambda_caller2(function_name) \
make_lambda_caller(function_name, function_name##_param, function_name##_)
Then, use them as below :
struct foo2_param {
int i = 0;
std::string folder{ “Abc” };
};
void foo2(const foo2_param& param) {
std::cout << param.i << " " << param.folder;
}
make_lambda_caller2(foo2)
void mycall2() {
foo2_([](auto & p) {
p.folder = "ARG";
});
}
Did I drink too much ?
Thanks for your article anyway, it's an excellent read.
Hi Pascal,
thanks for your feedback! The idea behind CreateFile is quite easy because all the mandatory parameters are intended to stay positional. With tags you can just make their intent clearer. You can end up with something like the following:
File CreateFile(path_t, const string& path, LambdaType&& constructor); // omitting templates because of WP
path_t is just the tag which describes the meaning of the next parameter.
Your examples are very interesting, I think they are a bit convoluted, though! The scope of the lambda approach is to be as easy and self-contained as possible, meaning that just a few lines of – say – infrastructural code behind the scenes are needed.
Some time after blogging this article, I found myself using in production another approach, variation of the classic “Named Parameter Idiom”. Basically, by clever application of variadics you can easily make mandatory parameter functions result in compile error if they are not called. For instance:
auto file = FileBuilder()
.Path("some-path"). // this is mandatory
.CreateFullPathIfMissing()
.SetAttribute("some-attribute")
.Build();
Using variadic templates you can set up a compile-time check of mandatory calls (e.g. Path). Also, you can check if the user calls a function more than once and break if you like. The code behind the scenes is clearly more difficult to understand, however the way to call the builder (e.g. what the user has to understand) is a well-known pattern, easy even for people not familiar with C++ (that was exactly my scenario).
Thanks for reading!
It’s worth mentioning that one very simple approach is to use C feature called “designated initializers”. It’s not really a part of C++ yet (but maybe in C++20 they’ll be) but gcc and clang support it. Though it seems that MSVC doesn’t.
Example
“`
struct Args
{
int a;
int b;
int c;
};
void f(Args args);
int main()
{
f({
.a = 3,
.b = 7,
.c = 15
});
}
“`
[…] Bring named parameters in modern C++ […]