Home

Awesome

Named parameters for C++14 [Build Status]

Here is another proof of concept to implement named parameters in C++.

Discl.: This experiment requires C++14 and support of n3599 (gcc and clang support this as a non standard extension). Tests are passing with g++4.9+ and clang 3.6+.

In a few words, this header only-library permits to write:

// service
template <typename ...Args>
void f(Args&& ...args)
{
    using namespace na::literals;
    // non mandatory
    auto && a = na::get("a"_na = 1    , std::forward<Args>(args)...);
    auto && c = na::get("c"_na = "bar", std::forward<Args>(args)...);
    // mandatory
    auto && b = na::get("b"_na        , std::forward<Args>(args)...); // a reference
    auto    d = na::get("d"_na        , std::forward<Args>(args)...); // a copy
    auto && e = na::get("e"_na        , std::forward<Args>(args)...); // a const-reference
    auto && i = na::get("i"_na        , std::forward<Args>(args)...); // a reference

    // do stuff with the parameters
    ++i;
}

// client
using namespace na::literals;
auto       v = std::vector<int>{ 1, 2, 3, 4, 5};
auto const x = std::vector<int>{ 11, 12, 13, 14, 15};
auto       i = 0;
// in no particular order
f( "d"_na=12.5, "a"_na=42, "c"_na="foo", "b"_na=v, "e"_na=x, "i"_na = i);
assert(i==1);

// without calling the optional parameters
f("d"_na=12.5, "b"_na=v, "e"_na=x, "i"_na = i);
assert(i==2);

Objectives

Design considerations

one parameter == one type

I went (as similar libraries do) to one parameter == one very specific type. This way, the variadic and generic list of parameters can be analysed at compile time.

In order to define those types, we could ask the end-user to explicitly define one type for each possible parameter for all the functions were s/he wants named parameters. While it's a good exercise to play with variadic template, this solution is not acceptable for the end-user.

Some libraries use macros to define those types on-the-fly. I went for C++11 user-defined literals. Well. Not exactly. The standard permits to have the literal either cooked for us, or still in their raw form. In order to define a type that can be used with templates, the raw form is necessary. Unfortunately, the standard does not offer raw literals on string. This is a gcc extension, that clang also supports (I haven't checked VC++ nor ICC, and I'm not sure whether it'll be integrated in future versions of the language).

Passing the value

In order to pass the value from the call site to a local variable acting as an argument, the string literal type builds and returns a proxy object we assign a value to. The proxy object stores a reference to the parameter value.

In order to avoid undesired copies, parameters are perfect-forwarded.

If we want to get a reference, a const reference, or a copy, we have to just say so:

auto        copy           = na::get(....);
auto      & ref            = na::get(....);
auto const& constref       = na::get(....);
auto const nonmutablecopy  = na::get(....);
auto    && ref_or_constref = ... // see literature on perfect forwarding

NB: it's not necessary to extract all the parameters passed.

Fetching parameters

This operation is done through a recursive analysis of the parameter types generated on the fly. The analysis is done on types at compile time.

We may require a na::is_set() function for optional parameters.

Default values

While it'll be easy to have dedicated functions, like get() and get_default(), or mandatory() and defaulted(), ..., I choose to have only one function in order to simplify user code.

Unlike usual C++ code, the default value cannot be declared in function prototypes, it has to be declared in function definitions where local values are bound to the arguments. I choose to reuse the "argument"_na = value syntax (and thus the proxy object). This way, we can know at compile time whether an argument is mandatory or not, and thus we can generate compile time errors on misuses.

Compile time errors

A few techniques have been used to provide compilation error messages as clean as possible:

The library currently detects:

When to use this library

According you're working in C++14 and with a compiler that provides the required compiler-extension.

This library can be used with any function. This means all functions would become template functions. This is not something we wish to systematize as it'll make all functions inline.

The best is to reserve it to functions that are already template and that have many parameters we don't wish to mix up.

Or also to constructors. Named parameters are a neat alternative to setters. It's easy to forget to call a setter: the compiler will never tell us about it. With named parameters, we cannot mix up arguments and parameters nor forget to set them to pertinent values.

Running the tests

Unit tests are provided in the test/ directory. They require boost and CMake+CTest.

In order to execute them, you'll have to run from your console:

mkdir build && cd build
# With your default compiler
cmake -DCMAKE_BUILD_TYPE=Release path/to/project/root && make && ctest
# To force the use of clang++:
CXX=clang++ cmake -DCMAKE_BUILD_TYPE=Release path/to/project/root && make && ctest

TO DO

See also

Copyright

(c) 2016-2017, Luc Hermitte, Boost Sofware License