Click here to Skip to main content
15,885,782 members
Articles / Programming Languages / C++

Named C++ Function Parameters, Yet Another Approach

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
11 Mar 2017CPOL8 min read 20.3K   150   13  
Rather insane yet simple approach to the named function parameters
Epigraph:
What's in a name? that which we call a rose
By any other name would smell as sweet

William Shakespeare, Romeo and Juliet

Motivation

I came across a number of different approaches to C++ named function parameters. Some of them look quite interesting, but I did not find anything similar to the approach I suggested for JavaScript. No wonder people look for the ways of having named arguments. This feature is quite attractive:

  • One doesn’t need to remember the order and meaning of arguments; dealing with comprehensible names is more suitable to human nature.
  • The code of the call statement becomes more readable; it makes it obvious to see the role each value passed to a method plays.
  • Some or all named arguments can be omitted from the call statement, then the default values are used.
  • Named arguments can be used in combination with positional ones in the same function call.

I tried to examine the possibility of doing something similar in C++, and it turned out to be trickier. At the same time, after this exercise, the usage part on the caller side looks very similar to my JavaScript samples. I think this matter is at least funny enough to be worth sharing.

Disclaimer:

I do understand that the technique I propose if not really a technique of passing parameters. It is limited and even fake, a purely textual imitation of passing-parameter syntax. I also understand that it has very little practical and no theoretical importance and can soon be rendered obsolete.

And yet, it works, harmless and not likely to be misused. It can be considered as a fun of playing with the language expressive capabilities.

The Idea

The first and the simplest implementation would do this:

template <typename T>
class NamedParameter {
public:

    NamedParameter<void>* operator =(const T &value) {
        this->value = value;
        return nullptr;
    } //operator =

    operator T() const {
        return value;
    } //operator T()

private:

    T value;

};

It may look weird enough. Apparently, using void template parameter, if used for the declaration of a member of a variable, will cause compilation error, because of the member T value, which could not compile with void T. This little problem is easy to solve; I’ll show it later. But why NamedParameter<void> is used? In a function declaration, it serves as a placeholder parameter, to mark the place where a set of named parameters can be passed in the calling code. It should evoke the idea of the “polymorphic indefinite-size array of named parameters”. Let’s see how the usage may look:

class Sample {
public:    
    NamedParameter<int> ip;
    NamedParameter<double> dp;
    void A(int a, double b, NamedParameter<void>*) {}
    void B(int a, NamedParameter<void>*, double b) {}
}; //class Sample

//...

static void Demo() {
    Sample s;
    // positional and named parameters can be mixed:
    s.A(1, 2.2, (s.ip = 3, s.dp = 4.4));
    // the order of named actual arguments doesn't matter:
    s.A(1, 2.2, (s.dp = 4.4, s.ip = 3));
    // ... and they don't need to be at the end:
    s.B(5, (s.dp = 6.6, s.ip = 7), 8.8);
    // round brackets are not needed if one passes only one value
    // to a named parameter:
    s.B(5, s.ip = 7, 8.8);
} //Demo

Note that the set of assignments to the instances of NamedParameter<…> is enclosed in the round brackets. It is not needed if only one value is passed to a named parameter object. The syntax requires that at least one of the named parameter members was used in the function call. The syntax is only suitable for class/struct members, but those function and named parameter members could be either instance of static members.

The round brackets seem to be unavoidable. I played with the idea of defining a comma (,) operator for NamedParameter, but it seems to be useless, because it cannot change the non-operator semantic of the parameter list syntax using comma, which prevail when some code is placed in the actual argument list.

At the same time, those enforced round brackets can be considered as a benefit: in this syntax, the set of assignment operators passing named parameters’ values don’t have to be at the end of parameter list; moreover, one could define more than such set in the same function parameter list.

Such function call code compiles correctly due to the fact that the NamedParameter assignment operator is defined as returning =NamedParameter<void>*=; this way, it matches the formal parameter definition.

This is how the mechanism of default function parameters can be implemented:

class Sample {
public:

   Sample() { setDefaults(); }

    NamedParameter<int> ip;
    NamedParameter<double> dp;

    void A(int a, double b, NamedParameter<void>*) {
        // use parameters...
        setDefaults();
    } //A

    void B(int a, NamedParameter<void>*, double b) {
        // use parameters...
        setDefaults();
    } //B

private:

    static const int defaultIp = 0;
    double defaultDp = acos(-1);
    void setDefaults() {
        ip = defaultIp;
        dp = defaultDp;
    } //setDefaults

};

In this code sample, the functions using named parameters guarantee that the unused parameters will have their default values, before each call. But this guarantee can be destroyed by explicit assignment of the values to the named argument members outside of the declaring class. Again, this can be considered as a way to deliberately change the defaults.

It is obvious that the syntax, or syntactic sugar, is quite a rough textual imitation of parameter passing mechanism. In fact, the real mechanism uses just the assignment semantics. So this sugar may taste no so sweet. And yet, it works and is quite harmful, if the developer of the declaring class (Sample in the above example) knows what happens. However, there is one aspect which may look as a little problem.

Here is the thing: the NamedParameter objects come with two operators, one for assignment and one for reading of the underlying value; and both are accessible outside of the declaring class. It can be considered questionable.

Of course, the developer of the declaring class using NamedParameter members can always avoid any side effects of reading the parameter value. The values can be used only temporarily, avoiding any unwanted access to the class’s internals. But should the named parameter members be write-only, as the semantics of the parameter-passing suggests?

I attempted to limit reading of the values outside the declaring class in two different ways, in two different namespaces; any of the ways can be chosen.

Preventing Read, Optional: Unassigned Exception

One approach can be applied during runtime:

namespace Named {

    template <typename T>
    class Parameter {
    public:

        Parameter<void>* operator =(const T &value) {
            this->value = value;
            assigned = true;
            return nullptr;
        } //operator =

        operator T() const {
            if (!assigned)
                throw unassigned_named_parameter();
            return value;
        } //operator T()

        void unassign() { assigned = false; }

        struct unassigned_named_parameter : private std::logic_error {
            unassigned_named_parameter() : std::logic_error(std::string()) {}
        }; //unassigned_named_parameter

    private:

        T value;
        bool assigned = false;

    }; //class Parameter

    // This specialization is just a guard against members like
    // Parameter<void> parameter;
    template<> class Parameter<void> {};

}

First of all, look at the template specialization for Named::Parameter<void>. It will prevent the failure to compile the declaration of the variable/member of this type, which is useless anyway. The only useful role of this specialized type is the definition of the set of named parameters of a function.

The parameter read operation outside of the declaring class can be prevented by having the named parameter member unassigned and throwing the exception at the attempt to read the value in the unassigned state. It can be used in combination with the mechanism of default function parameters. The best thing in this approach is that it can be optional: if unassign() is never called, it brings us to the simplest naive implementation shown above. The problems of the runtime-based approach are obvious. At best, it could be used as a fool-proof measure during the development. At the same time, its benefit is the simplicity, especially if unassign() is never called.

For the usage samples, please see “NamedParametersDemo.h”.

The alternative approach would be making the named parameter objects purely write-only outside of the declaring class, but it also some little problems.

Another Alternative: Private Access to Read

So, the alternative approach is to pass the type of declaring class as an additional template parameter and make it a friend in the implementation of the named parameter. Note that it is only possible with C++11 or later standard.

In this case, the read access (operator T()) can be made private:

namespace NamedWriteonly {

    template <typename T, typename OWNER = void>
    class Parameter {
    public:

        Parameter<void>* operator =(const T &value) {
            this->value = value;
            return nullptr;
        } //operator =

    private:

        operator T() const {
            return value;
        } //operator T()

        T value;
        friend OWNER; // since C++11

    }; //class Parameter

    // This specialization is just a guard against members/variables like
    // NamedWriteOnly::Parameter<void> parameter;
    template<> class Parameter<void> {};

}

One little problem of this approach is a bit more complicated and less obvious usage in the declaring class, the need to pass the type as a template parameter. This is how it looks:

class PrivateReadSample {
public:

    NamedWriteonly::Parameter<int, PrivateReadSample> ip;
    NamedWriteonly::Parameter<double, PrivateReadSample> dp;

    void A(int a, NamedWriteonly::Parameter<void>*, double b) {
        aValue = a;
        bValue = b;
        ipValue = ip;
        dpValue = dp;
    } //A

    void ShowAssignmentResult() {
        printf(
            "Parameter a: %d; parameter ip: %d; parameter dp: %g; parameter b: %g\n",
            aValue, ipValue, dpValue, bValue);
    } //ShowAssignmentResult

private:

    int ipValue, aValue;
    double dpValue, bValue;

};

If the second template parameter is incorrect or missing, the declaration itself will compile, but it would make the named parameter value unreadable where it should be read, rendering the mechanism virtually useless.

Type Limitations

One apparent limitation to the underlying named parameter type T is some class with deleted parameterless constructor. This constructor, default or not, should present, otherwise the declaration of Parameter::value would fail.

Compatibility and Build

All the alternative slightly different solutions are contained in just one of the two files: “NamedParameters.h”, or “NamedParametersWriteonly.h”. The first and the simplest naive solution, still quite usable, can be taken from “ArticleCodeSamples.h”. Those classes can be added to any project.

The compiler should support C++11 or later standard. For GCC, this is an option which should be set to -std=c++11 or, say, -std=c++14.

The demo project is provided in two forms: 1) Visual Studio 2015 solution and project using Microsoft C++ compiler and Clang — see “ CppNamedParameters.sln” and 2) Code::Blocks project using GCC — “CppNamedParameters.cbp”. For all other options, one can assemble a project or a make file by adding all “*.h” and “*.cpp” files in the code directory “CppNamedParameters”.

I tested the code with Visual Studio 2015, Clang 4.0.0, GCC 5.1.0.

The C++ options included “disable language extensions” (/Za for Microsoft and Clang), which seems to be essential for Microsoft. However, with this option, one weird Microsoft problem is the failure to compile “//” comments at the end of a file; the problem can be solved, for example, by adding an empty line at the end of the file; I set up a “Microsoft guard” in the form of “/* … */” at the end of each file.

References

  1. Marco Arena, Bring named parameters in modern C++
  2. More C++ Idioms/Named Parameter
  3. Justin Van Horne, C++11 named parameters using operator suffixes and variadic templates
  4. S A Kryukov, Named Arguments for JavaScript Functions, Yet Another Approach
  5. S A Kryukov, Named Arguments for JavaScript Functions, Part Two: Going Structured

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Physics, physical and quantum optics, mathematics, computer science, control systems for manufacturing, diagnostics, testing, and research, theory of music, musical instruments… Contact me: https://www.SAKryukov.org

Comments and Discussions

 
-- There are no messages in this forum --