Вариадическое назначение функций и аргументов в C / C ++ - PullRequest
5 голосов
/ 13 мая 2010

Мне было интересно, можно ли в языке C / C ++ передавать аргументы функции в форме ключ-значение. Например, в Python вы можете сделать:

def some_function(arg0 = "default_value", arg1):
    # (...)

value1 = "passed_value"
some_function(arg1 = value1)

Таким образом, альтернативный код в C может выглядеть так:

void some_function(char *arg0 = "default_value", char *arg1)
{
    ;
}

int main()
{
    char *value1 = "passed_value";
    some_function(arg1 = value1);
    return(0);
}

Таким образом, аргументы для использования в some_function были бы:

arg0 = "default_value"

arg1 = "прошло_значение"

Есть идеи?

Ответы [ 7 ]

8 голосов
/ 13 мая 2010

Вот решение C99, использующее составные литералы и переменные макросы:

#include <stdio.h>

#define some_func(...) some_func_((struct some_func_args_){ __VA_ARGS__ })

struct some_func_args_
{
    const char *arg1;
    const char *arg2;
};

static void some_func_(struct some_func_args_ args)
{
    if(!args.arg1) args.arg1 = "default";
    printf("---\narg1 = %s\narg2 = %s\n", args.arg1, args.arg2);
}

int main(void)
{
    some_func("foo", "bar");
    some_func(.arg1 = "spam");
    some_func(.arg2 = "eggs");
    return 0;
}
6 голосов
/ 13 мая 2010

Именованные аргументы не поддерживаются в C, но вы можете эмулировать именованные параметры, используя переменную функцию (хотя вы теряете безопасность типов):

#include <stdarg.h>

void do_sth (int foo, ...)
{
    int baz = 7;             /* "baz" argument */
    const char *xyz = "xyz"; /* "xyz" argument */

    /* Parse named parameters */
    va_list ap;
    va_start (ap, foo);
    for (;;) {
        const char *key = va_arg (ap, char *);
        if (key == NULL) {
            /* Terminator */
            break;
        } else if (strcmp (key, "baz") == 0) {
            baz = va_arg (ap, int);
        } else if (strcmp (key, "xyz") == 0) {
            xyz = va_arg (ap, char *);
        } else {
            /* Handle error */
        }
    }
    va_end (ap);

    /* do something useful */
}

do_sth (1, NULL);                             // no named parameters
do_sth (2, "baz", 12, NULL);                  // baz = 12
do_sth (3, "xyz", "foobaz", NULL);            // xyz = "foobaz"
do_sth (4, "baz", 12, "xyz", "foobaz", NULL); // baz = 12, xyz = "foobaz"

Только не забудьте завершить список необязательных аргументов значением NULL. Вы также можете использовать некоторые ENUM в качестве ключей вместо символьных строк.

Этот метод используется, например, в GTK +:

6 голосов
/ 13 мая 2010

Вы можете эмулировать это в C ++ с помощью этого:

struct params {
   string foo_;
   double bar_;
   short  xxx_;
   params() : foo_("123"), bar_(3.1415), xxx_(42) {} // default parameters
   params& foo(string s) {foo_=s;return *this;}
   params& bar(double x) {bar_=x;return *this;}
   params& xxx(short x) {xxx_=x;return *this;}
};

void some_function(params const & p);

int main() {
   some_function(params().bar(99.9).xxx(23));
}

Но ИМХО, это не стоит усилий. Слишком много котельной плиты.

Если я правильно помню, книга Страуструпа "Дизайн и эволюция C ++" содержит раздел, в котором обсуждается этот запрос функции "именованных аргументов". Заключение было чем-то вроде: не очень хорошая идея. Проверьте это, если хотите узнать подробности.

3 голосов
/ 13 мая 2010

Это не доступно в vanilla C или C ++. Однако есть библиотека C ++ Boost, которая позволяет вам делать это для написанных вами функций: Boost.Parameter . Чтобы взять один из их примеров, его использование выглядит примерно так:

myclass x("bob", 3);                     // positional
myclass y(_index = 12, _name = "sally"); // named
myclass z("june");                       // positional/defaulted

Однако реализация этого для ваших функций выглядит несколько сложной задачей. Вы можете решить, что это не стоит усилий.

1 голос
/ 13 мая 2010

Нет, вы не можете передавать параметры по имени в C или C ++.

Оба языка поддерживают аргументы по умолчанию для параметров trailing в функции. То есть любой аргумент функции может иметь значение по умолчанию, но все последующие аргументы после того, как найдено первое значение по умолчанию, также должны иметь значения по умолчанию.

Например, все следующие действительны:

void f(int a, int b = 0);
void g(double a = 1, double b = 2);
void h(int a = 3, int b = 2, int c = 1, int d = 0);
void i(float a, float b, float c = 1, float d = 2);

Но ни одно из следующего не является действительным:

void j(int a = 1, int b);
void k(int a, int b = 1, int c);
void l(int a = 2, int b = 1, int c);

Причина проста: поскольку вы не можете передавать параметры по имени в C и C ++, единственный способ узнать, какие параметры использовать значения по умолчанию, а какие использовать переданные значения, - это присвоить переданные значения параметрам в порядке и затем использовать значения по умолчанию (по порядку) для всего, что осталось.

Итак, следующие вызовы действительны при использовании указанных выше функций:

f(0); // calls f(0, 1);
g(); // calls g(1,2);
g(10); // calls g(10,2);
h(); // calls h(3,2,1,0);
h(1,2); // calls h(1,2,1,0);
0 голосов
/ 13 мая 2010
#define call_foo(...) \
   do { \
      int first; \
      int second; \
      int third;  \
      (__VA_ARGS__); \
      foo(first, second, third); \
   } while (0)

....

int main(void) {
   call_foo(first=9, third=5, second=3);
}

Получение возвращаемых значений без затруднений.

Это позволит вам делать очень глупые вещи (которые я ненавижу), такие как указание значений по умолчанию для ваших аргументов:

#define call_foo(...) \
   do { \
      int first = default_first; \
      int second; \
      int third;  \
      (__VA_ARGS__); \
      foo(first, second, third); \
   } while (0)

Это не удастся, если вы сделаете:

   int first = 9;
   call_foo(first=first, third=5, second=3);

потому что в расширении макроса first=first будет означать инициализацию локального first с самим собой. Я думал о попытке обойти это с помощью конкатенации препроцессора, но это становится более сложным. Вам нужно будет указать то же количество параметров макроса, что и для параметров функции.

#define call_foo(x, y , z) \
   do { \
      int call_foo_first; \
      int call_foo_second; \
      int call_foo_third;  \
      call_foo_##x;   \
      call_foo_##y;   \
      call_foo_##z;   \
      foo(call_foo_first, call_foo_second, call_foo_third); \
   } while (0)

Строка call_foo_##x; превратится в call_foo_first=first;, если вызывается в предыдущем примере, поэтому у каждого символа есть свое уникальное имя. Это делает трюк с аргументом по умолчанию более неловким, потому что вам нужно указать что-то, чтобы заполнить это место в списке аргументов макроса.

Если макрос был определен с аргументом по умолчанию для first, тогда:

call_foo(first, third=7, second=8);

Это вынуждает вас перечислять все параметры (или, по крайней мере, что-то, что приводит к допустимому C - вы могли бы перечислить секунду дважды, но вы могли бы сделать это в любом случае), чтобы использовать этот макрос.

Я думаю, что вы должны иметь возможность расширить последнюю версию этого для переменных макросов / функций, перечисленных в качестве аргумента переменной, но необязательные параметры все равно должны быть переданы в конце списка, и вам придется использовать все обязательные места, прежде чем вы получите необязательные аргументы.

0 голосов
/ 13 мая 2010

C ++ only решение: Вы можете использовать boost :: any, std :: map и std :: string для создания объекта с возможными аргументами.

#include <iostream>
#include <map>
#include <string>

#include "boost/any.hpp"

using namespace std;

void variableArguments(map<string, boost::any> arguments){
    cout << ">>> variableArguments begins" << endl;

    if(arguments.find("intParameter") != arguments.end()){
    cout << "Found int parameter: " 
         << boost::any_cast<int>(arguments["intParameter"]) << endl;
    }

    if(arguments.find("stringParameter") != arguments.end()){
    cout << "Found string parameter: " 
         << boost::any_cast<string>(arguments["stringParameter"]) << endl;
    }

    cout << "<<< variableArguments ends" << endl;

}

int main(int argc, char *argv[])
{

    map<string, boost::any> argMap;
    argMap["intParameter"] = 5;
    argMap["stringParameter"] = string("MyString");

    variableArguments(argMap);

    argMap.erase(argMap.find("intParameter"));

    variableArguments(argMap);

    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...