Я задавался вопросом, возможно ли на языке 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"
Какие-либо идеи?
Вот решение 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;
}
Это недоступно в стандартном C или C ++. Однако существует библиотека C ++ Boost, которая позволяет делать это для функций, которые вы пишете: Boost.Parameter . Если позаимствовать один из их примеров, его использование выглядит примерно так:
myclass x("bob", 3); // positional
myclass y(_index = 12, _name = "sally"); // named
myclass z("june"); // positional/defaulted
Однако реализация этого для ваших функций выглядит несколько запутанной. Вы можете решить, что это не стоит усилий.
#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 - вы могли бы указать второй дважды , но вы все равно могли бы это сделать), чтобы использовать этот макрос.
Я думаю, вы сможете расширить последнюю версию этого для макросов / функций из списка аргументов переменных, но необязательные параметры все равно придется передавать в конце списка, и вам придется использовать все обязательные места, прежде чем вы перейдете к необязательным аргументам.
Решение только для 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;
}
Вы можете эмулировать это в 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++» содержит раздел, в котором обсуждается эта особенность запроса «именованных аргументов». Вывод был примерно таким: не очень хорошая идея.Проверьте это, если вам нужны подробности.
Именованные аргументы не поддерживаются в 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 +:
Нет, вы не можете передавать параметры по имени в 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);