简体   繁体   中英

How can we call a function with “parameter=value” in C++?

When reading codes, we will find some functions like this.

g_spawn_async(NULL, new_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);

I think nobody can figure out what is the meaning of every parameter. In order to understand the code, we have to find the declaration of the function.

gboolean    g_spawn_async               (const gchar *working_directory,
                                         gchar **argv,
                                         gchar **envp,
                                         GSpawnFlags flags,
                                         GSpawnChildSetupFunc child_setup,
                                         gpointer user_data,
                                         GPid *child_pid,
                                         GError **error);

How can we call a function like the following format in C++?

g_spawn_async(working_directory=NULL,
              argv=new_argv,
              envp=NULL,
              flags=G_SPAWN_SEARCH_PATH,
              child_setup=NULL,
              user_data=NULL,
              child_pid=NULL,
              error=NULL);

I think this one will be more readable and I can understand the code without looking for the declaration of the function.

I know Python can do this. How can C++ do this?

C++ doesn't support this natively, so you can't do it with just any old existing function. If you're creating your own API though, you can use what's called the Named Parameter Idiom to emulate it. The example from the link:

File f = OpenFile("foo.txt")
           .readonly()
           .createIfNotExist()
           .appendWhenWriting()
           .blockSize(1024)
           .unbuffered()
           .exclusiveAccess();

This is not possible in C or C++.

I understand your pains with this. I personally think that it is a sign of bad design to have a function take over 9000 arguments, especially if most of them are NULL or placeholder values. Many POSIX-standardized functions for example take some kind of struct that accumulates all necessary values into one, easy to understand argument.

No, this can't be done. But you can assign the NULL values to the variables and then pass them as parameters if it helps with your readability!

g_spawn_async(working_directory, argv, envp,flags,child_setup , user_data, child_pid, error);

The BOOST parameter library can help you. It works well and is portable.... See http://www.boost.org/doc/libs/1_54_0/libs/parameter/doc/html/index.html

It's certainly possible. It's not even particularly difficult, but it does involve a lot of code. Something like the following could be used:

enum MyFuncParamId
{
    myA,
    myB,
    myC,
    myD,
    unknown
};

class MyFuncParam
{
    union OneParam
    {
        double aOrB;
        C c;
        int d;

        OneParam() {}
        ~OneParam() {}
    };
    OneParam myParam;
    MyFuncParamId myId;

public:
    MyFuncParam( MyFuncParamId id, double value )
        : myId( id )
    {
        switch ( myId ) {
        case myA:
        case myB:
            myParam.aOrB = value;
            break;

        case myC:
            assert( 0 );
            abort();

        case myD:
            myParam.d = value;
            break;
        }
    }
    MyFuncParam( MyFuncParamId id, C const& value )
        : myId( id )
    {
        switch ( myId ) {
        case myA:
        case myB:
        case myD:
            assert( 0 );
            abort();

        case myC:
            new (&myParam.c) C( value );
            break;
        }
    }
    MyFuncParam( MyFuncParamId id, int value )
        : myId( id )
    {
        switch ( myId ) {
        case myA:
        case myB:
            myParam.aOrB = value;
            break;

        case myC:
            assert( 0 );
            abort();

        case myD:
            myParam.d = value;
            break;
        }
    }
    MyFuncParam( MyFuncParam const& other )
        : myId( other.myId )
    {
        switch ( myId ) {
        case myA:
        case myB:
            myParam.aOrB = other.myParam.aOrB;
            break;
        case myC:
            new (&myParam.c) C( other.myParam.c );
            break;
        case myD:
            myParam.d = other.myParam.d;
            break;
        }
    }
    ~MyFuncParam()
    {
        switch( myId ) {
        case myC:
            myParam.c.~C();
            break;
        }
    }
    MyFuncParam& operator=( MyFuncParam const& ) = delete;
    friend class MyFuncParamGroup;
};

class MyFuncRouter
{
    MyFuncParamId myId;
public:
    MyFuncRouter( MyFuncParamId id ) : myId( id ) {}

    MyFuncParam operator=( double value )
    {
        return MyFuncParam( myId, value );
    }

    MyFuncParam operator=( C const& value )
    {
        return MyFuncParam( myId, value );
    }

    MyFuncParam operator=( int value )
    {
        return MyFuncParam( myId, value );
    }
};

static MyFuncRouter a( myA );
static MyFuncRouter b( myB );
static MyFuncRouter c( myC );
static MyFuncRouter d( myD );

struct MyFuncParamGroup
{
    bool aSet;
    bool bSet;
    bool cSet;
    bool dSet;
    double a;
    double b;
    C c;
    int d;
    MyFuncParamGroup()
        : aSet( false )
        , bSet( false )
        , cSet( false )
        , dSet( false )
    {
    }
    void set( MyFuncParam const& param )
    {
        switch ( param.myId ) {
        case myA:
            assert( !aSet );
            aSet = true;
            a = param.myParam.aOrB;
            break;
        case myB:
            assert( !bSet );
            bSet = true;
            b = param.myParam.aOrB;
            break;
        case myC:
            assert( !cSet );
            cSet = true;
            c = param.myParam.c;
            break;
        case myD:
            assert( !dSet );
            dSet = true;
            d = param.myParam.d;
            break;
        }
    }
};

void
myFunc(
    MyFuncParam const& p1,
    MyFuncParam const& p2,
    MyFuncParam const& p3,
    MyFuncParam const& p4)
{
    MyFuncParamGroup params;
    params.set( p1 );
    params.set( p2 );
    params.set( p3 );
    params.set( p4 );
    std::cout << "a = " << params.a
        << ", b = " << params.b
        << ", c = " << params.c
        << ", d = " << params.d
        << std::endl;
}

Notes:

  1. I've used C++11 here. The same thing can be done in earlier versions of C++, by replacing the type C in the union with unsigned char c[sizeof( C )]; , adding something to the union to ensure correct alignment (if necessary), and a lot of type casting.

  2. This would be a lot simpler with boost::variant (instead of the union ) and boost::optional (in MyFuncParamGroup ). I didn't have Boost available, so I did most of what they do explicitly. (Which of course, makes the code a lot longer.)

  3. I've intentionally used two parameters with the same type, and a user defined type ( C ) with non-trivial constructors, to show how these are handled.

  4. You'd probably want a bit more encapsulation, and a bit more error checking.

But the real question is: do you really want to go this route? The amount of code increases linearly with the number of parameters. And with any decent editor, you can temporarily put the parameter list from the function declaration to the right of your screen, and fill out the parameters to the left, directly along side of the parameter declaration. (In gvim, I'll usually use block editing mode for this, but :vsplit can also be used.)

Named parameters are very useful and I was even considering that in a language they should be the only way to call a function except for a single obvious parameter if that is present.

sin(x)               // the obvious main parameter
sin(x, unit=DEGREE)  // any other must be named

C++ unfortunately doesn't have them.

More importantly C++ lacks the metaprogramming ability needed to be able to implement them.

While there are tricks that can try to somewhat mimic named parameters and even if the resulting code can look almost reasonable, what is totally indecent is the code you need to write to get that simulation and that there is no way in C++ to generate that code.

What I mean is that while it's possible to write a function accepting named parameters emulated reasonably, the code for the function itself is hideous and cannot be automatically generated in C++. This means that no one will write functions that way so the feature is still not present.

My guess is that named parameters are missing because of a mix of ignorance (they existed way before C++ was invented, probably even before C) and of pride (even in C++11 the language metaprogramming abilities are pathetic and things trivial like say enumerating at compile time the members of a structure or the parameters of a function is impossible).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM