Функции Variadic и присвоение аргументов в C/C++

Я задавался вопросом, возможно ли на языке 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 = "passed_value"

Какие-либо идеи?

5
задан Rizo 13 May 2010 в 10:48
поделиться

7 ответов

Вот решение 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;
}
8
ответ дан 18 December 2019 в 06:49
поделиться

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

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

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

3
ответ дан 18 December 2019 в 06:49
поделиться
#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 будет означать инициализировать локальный сначала самим собой. Я думал о том, чтобы попытаться обойти это с помощью конкатенации препроцессора, но это становится более сложным. Вам нужно будет указать такое же количество параметров макроса, что и параметры функции.

#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; , если бы вызывалась в предыдущем примере, поэтому каждый символ имеет собственное уникальное имя. Это делает уловку аргумента по умолчанию более неудобной, потому что вам нужно будет указать что-то, чтобы заполнить это место в списке аргументов макроса.

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

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

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

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

0
ответ дан 18 December 2019 в 06:49
поделиться

Решение только для C ++: Вы можете использовать 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;
}
-1
ответ дан 18 December 2019 в 06:49
поделиться

Вы можете эмулировать это в 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++» содержит раздел, в котором обсуждается эта особенность запроса «именованных аргументов». Вывод был примерно таким: не очень хорошая идея.Проверьте это, если вам нужны подробности.

6
ответ дан 18 December 2019 в 06:49
поделиться

Именованные аргументы не поддерживаются в 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
ответ дан 18 December 2019 в 06:49
поделиться

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

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

Например, все следующие аргументы являются допустимыми:

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);
1
ответ дан 18 December 2019 в 06:49
поделиться