Bring named parameters in modern C++

Posted: December 16, 2014 in Programming Recipes
Tags: , ,

[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:

  1. logger and comparator are global constants; the assignment operator just returns a wrapped copy of the assigned value,
  2. 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++?

Comments
  1. blackibiza says:

    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.

    • Marco Arena says:

      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).

    • jgmccabe says:

      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.

  2. masterbuzzsaw says:

    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.

  3. mjanes says:

    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 …

    • mjanes says:

      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 );
      // …

      • Marco Arena says:

        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!

  4. Marius says:

    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.

  5. Jim says:

    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.

  6. Martin says:

    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) );
    }

  7. Hello

    You may be interested by some of my work on the same subject : https://github.com/duckie/named_types

    Regards

  8. Pascal Thomet says:

    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.

    • Pascal Thomet says:
      My code did not survive the html filters, so here is a link to a gist : .gist table { margin-bottom: 0; } This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters // This code explores some variations around the lambda proposition in the following article by Marco Arena // https://marcoarena.wordpress.com/2014/12/16/bring-named-parameters-in-modern-cpp/ #include <iostream> #include <string> //Version 1 (without macros) // Will be called like this : // // foo_([](auto & p) { // p.msg = "The answer is"; // }, // 42 // required params go here // ); struct foo_param { int value; std::string msg { "Default" }; // value is a required param since it is a param of the constructor // msg is an optional param foo_param(int _v) { value = _v; } }; void foo(const foo_param& param) { std::cout << param.msg << " " << param.value << std::endl; } template<typename paramModifierFunction, typename… Args> auto foo_(paramModifierFunction fun, Args… args) { foo_param p(args…); fun(p); return foo(p); } //Version 2 (with helper macro in order to create the adapted foo2_ version) // Will be called like foo : // // foo2_([](auto & p) { // p.msg = "The answer is"; // }, // 42 // required params go here // ); #define make_named_caller_detailed(function_name, param_name, dst_function_name) template<typename ParamModifierFunction, typename… Args> auto dst_function_name(ParamModifierFunction paramModifierFunction, Args… args) { param_name param(args…); paramModifierFunction(param); return function_name(param); } #define make_named_caller(function_name) make_named_caller_detailed(function_name, function_name##_param, function_name##_) // How to use them struct foo2_param { std::string msg { "Default" }; int value = 0; foo2_param(int _v) { value = _v; } }; void foo2(const foo2_param & param) { std::cout << param.msg << " " << param.value << std::endl; } make_named_caller(foo2) // Version 3 : even more macro abuse, since we use a macro to hide the lambda creation. // It will be called like this // named_call( foo3, _.msg = "The answer is"; _.value= 42; ); // It does *not* work with required params. #define named_call(function_name, code) function_name##_( [] (auto & _) { code }); #define COMMA , struct foo3_param { std::string msg { "Default" }; int value = 0; }; void foo3(const foo3_param & param) { std::cout << param.msg << " " << param.value << std::endl; } make_named_caller(foo3) // Version 4 : a version of named_call which is also compatible with required params // it uses ##__VA_ARGS__ in order to remove the extra comma if needed. // see https://stackoverflow.com/questions/5588855/standard-alternative-to-gccs-va-args-trick) // Note : it would be better to have the required params before the optional ones : see named_call3 below #define named_call2(function_name, code, …) function_name##_( [] (auto & _) { code; }, ##__VA_ARGS__); struct foo6_param { std::string msg { "Default" }; std::string msg2 { "" }; int value1 = 0; int value2 = 0; foo6_param(int _v1, int _v2) { value1 = _v1; value2 = _v2;} }; void foo6(const foo6_param & _) { std::cout << _.msg << " " << _.msg2 << _.value1 + _.value2 << std::endl; } make_named_caller(foo6) // Version 5 : another version of named_call which is also compatible with required params // in which the required params come before the optional ones. The macros below are used to retrieve the last macro arg. // This version is incompatible with visual studio ! // // Note : in order to make a version compatible with MSVC, some inspiration can be taken here : // https://stackoverflow.com/questions/26682812/argument-counting-macro-with-zero-arguments-for-visualstudio-2010/26685339#26685339 // count arguments #define M_NARGS(…) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, …) N #define M_NARGS_PLUS1(…) M_NARGS_PLUS1_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define M_NARGS_PLUS1_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, …) N #define M_NARGS_MINUS1(…) M_NARGS_MINUS1_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define M_NARGS_MINUS1_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, …) N #define M_NARGS_MINUS2(…) M_NARGS_MINUS2_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define M_NARGS_MINUS2_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, …) N // utility (concatenation) #define M_CONC(A, B) M_CONC_(A, B) #define M_CONC_(A, B) A##B #define M_GET_ELEM(N, …) M_CONC(M_GET_ELEM_, N)(__VA_ARGS__) #define M_GET_ELEM_0(_0, …) _0 #define M_GET_ELEM_1(_0, _1, …) _1 #define M_GET_ELEM_2(_0, _1, _2, …) _2 #define M_GET_ELEM_3(_0, _1, _2, _3, …) _3 #define M_GET_ELEM_4(_0, _1, _2, _3, _4, …) _4 #define M_GET_ELEM_5(_0, _1, _2, _3, _4, _5, …) _5 #define M_GET_ELEM_6(_0, _1, _2, _3, _4, _5, _6, …) _6 #define M_GET_ELEM_7(_0, _1, _2, _3, _4, _5, _6, _7, …) _7 #define M_GET_ELEM_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, …) _8 #define M_GET_ELEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, …) _9 #define M_GET_ELEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, …) _10 #define M_GET_UPTO_ELEM(N, …) M_CONC(M_GET_UPTO_ELEM_, N)(__VA_ARGS__) #define M_GET_UPTO_ELEM_0(_0, …) #define M_GET_UPTO_ELEM_1(_0, _1, …) _0 #define M_GET_UPTO_ELEM_2(_0, _1, _2, …) _0, _1 #define M_GET_UPTO_ELEM_3(_0, _1, _2, _3, …) _0, _1, _2 #define M_GET_UPTO_ELEM_4(_0, _1, _2, _3, _4, …) _0, _1, _2, _3 #define M_GET_UPTO_ELEM_5(_0, _1, _2, _3, _4, _5, …) _0, _1, _2, _3, _4 #define M_GET_UPTO_ELEM_6(_0, _1, _2, _3, _4, _5, _6, …) _0, _1, _2, _3, _4, _5 #define M_GET_UPTO_ELEM_7(_0, _1, _2, _3, _4, _5, _6, _7, …) _0, _1, _2, _3, _4, _5, _6 #define M_GET_UPTO_ELEM_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, …) _0, _1, _2, _3, _4, _5, _6, _7 #define M_GET_UPTO_ELEM_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, …) _0, _1, _2, _3, _4, _5, _6, _7, _8 #define M_GET_UPTO_ELEM_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, …) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 // Get last argument – placeholder decrements by one #define M_GET_LAST(…) M_GET_ELEM(M_NARGS(__VA_ARGS__), _, __VA_ARGS__ ,,,,,,,,,,,) #define M_GET_ALL_BUT_LAST(…) M_GET_UPTO_ELEM(M_NARGS_MINUS1(__VA_ARGS__), __VA_ARGS__ ,,,,,,,,,,,) #define named_call3(function_name, …) function_name##_( [] (auto & _) { M_GET_LAST(__VA_ARGS__); }, M_GET_ALL_BUT_LAST( __VA_ARGS__ )); // Main int main() { // Version 1 ; standard lamdda std::cout << "Version 1 : standard lambda" << std::endl; foo_([](auto & p) { p.msg = "The answer is"; }, 42 // required params go here ); // Version 2 ; standard lamdda, defined by macro std::cout << "Version 2 : Standard lambda, defined by macro" << std::endl; foo2_([](auto & p) { p.msg = "The answer is"; }, 42 // required params go here ); // // Version 3 : named_call // std::cout << "Version 3.1 : using named_call" << std::endl; named_call( foo3, _.msg = "The answer is"; _.value= 42; ); // More complex case, when you need to use comma // in your code, replace it by another macro (sigh…) std::cout << "Version 3.2 : using named_call and comma" << std::endl; named_call( foo3, _.msg = "The answer is"; int a = 37 COMMA b = 5; _.value= a + b; ); // a trailing comma is required in order to use named_call with all // default params values. std::cout << "Version 3.3 : call with all default values" << std::endl; named_call( foo3, ); // However, this is equivalent to calling foo5(foo5_param()) foo3( foo3_param() ); // // Version 4 : named_call2 // named_call2 can also call a function with required params // the required params come last, after the optional ones and they are separated by "," // the optional params are separated by ";" std::cout << "Version 4.1 : using named_call2" << std::endl; named_call2( foo6, _.msg = "Ladies and gentlemen"; _.msg2 = "the answer is ", 35, 7 ); // when using only required params, named_call2 will require a double comma (, ,) std::cout << "Version 4.2 : using named_call2 with only required params" << std::endl; named_call2( foo6, , 35, 7 ); // However this line was equivalent to : foo6(foo6_param(35, 7)); // // Version 5 : named_call3 // named_call3 permits to specify the required parameters first. // it will not work with msvc std::cout << "Version 5.1 : using named_call3 with both required and optional params" << std::endl; named_call3(foo6, 35, 7, _.msg = "Ladies and gentlemen,"; _.msg2= "the answer is "); // when using only required params, named_call3 will require an ending std::cout << "Version 5.2 : using named_call3 with only required params" << std::endl; named_call3(foo6, 35, 7, ); // however this is equivalent to foo6(foo6_param(35, 7)); } view raw lambda_named_params.cpp hosted with ❤ by GitHub
    • Marco Arena says:

      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!

  9. Dmitriy says:

    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
    });
    }
    “`

  10. […] Bring named parameters in modern C++ […]

Leave a comment