Переменное количество аргументов в C ++?


293

Как я могу написать функцию, которая принимает переменное число аргументов? Это возможно, как?


49
На этот раз с C ++ 11 ответы на этот вопрос будут сильно отличаться
K-balloon

1
@ K-balloon Я также добавил примеры на C ++ 11, так как недавний вопрос недавно задавал эту же тему, и я чувствовал, что этот вопрос нужен, чтобы оправдать его закрытие stackoverflow.com/questions/16337459/…
Shafik Yaghmour

1
К моему ответу также добавлены опции до C ++ 11 , так что теперь он должен охватывать большинство доступных вариантов.
Шафик Ягмур

@ K-балл, нет никакого способа сделать это в C ++, если вам нужен принудительный тип аргумента .. нет конструкции типа foo (int ... values): / Если вас не интересуют типы, тогда да, шаблоны с переменным числом аргументов в C ++ 11 отлично работает
Graywolf

Ответы:


152

Вы, вероятно, не должны, и вы, вероятно, можете делать то, что хотите, более безопасным и простым способом. Технически, чтобы использовать переменное число аргументов в C, вы включаете stdarg.h. Из этого вы получите va_listтип, а также три функции, которые работают с ним va_start(), va_arg()и , и va_end().

#include<stdarg.h>

int maxof(int n_args, ...)
{
    va_list ap;
    va_start(ap, n_args);
    int max = va_arg(ap, int);
    for(int i = 2; i <= n_args; i++) {
        int a = va_arg(ap, int);
        if(a > max) max = a;
    }
    va_end(ap);
    return max;
}

Если вы спросите меня, это беспорядок. Выглядит плохо, небезопасно и полно технических деталей, которые не имеют ничего общего с тем, чего вы концептуально пытаетесь достичь. Вместо этого рассмотрите возможность использования перегрузки или наследования / полиморфизма, шаблона компоновщика (как в operator<<()потоках) или аргументов по умолчанию и т. Д. Все они безопаснее: компилятор узнает больше о том, что вы пытаетесь сделать, поэтому есть больше случаев, когда он может остановиться Вы, прежде чем оторвать ногу.


7
Предположительно, вы не можете передавать ссылки на функцию varargs, потому что компилятор не будет знать, когда передавать по значению, а когда по ссылке, и потому что базовые макросы C не обязательно будут знать, что делать со ссылками - уже есть ограничения на то, что Вы можете перейти в функцию C с переменными аргументами из-за таких вещей, как правила продвижения.
Джонатан Леффлер

3
необходимо ли предоставить хотя бы один аргумент перед ...синтаксисом?
Лазер

3
@Lazer - это не требование к языку или библиотеке, но стандартная библиотека не дает вам возможности определить длину списка. Вам нужен вызывающий абонент, чтобы дать вам эту информацию, или как-то самому разобраться. В случае printf(), например, функция анализирует строковый аргумент для специальных токенов, чтобы выяснить, сколько дополнительных аргументов следует ожидать в списке переменных аргументов.
Вильгельмтелл

11
вы, вероятно, должны использовать <cstdarg>в C ++ вместо<stdarg.h>
newacct

11
Переменное число аргументов отлично подходит для отладки или для функций / методов, которые заполняют некоторый массив. Кроме того, он отлично подходит для многих математических операций, таких как max, min, sum, average ... Это не беспорядок, когда вы не связываетесь с ним.
Томаш Зато - Восстановить Монику

395

В C ++ 11 у вас есть две новые опции, как указано в справочной странице функций Variadic в разделе « Альтернативы» :

  • Шаблоны Variadic также можно использовать для создания функций, которые принимают переменное число аргументов. Часто они являются лучшим выбором, потому что они не налагают ограничений на типы аргументов, не выполняют целочисленное продвижение и продвижение с плавающей запятой и безопасны для типов. (начиная с C ++ 11)
  • Если все переменные аргументы имеют общий тип, std :: initializer_list предоставляет удобный механизм (хотя и с другим синтаксисом) для доступа к переменным аргументам.

Ниже приведен пример, показывающий обе альтернативы ( смотрите вживую ):

#include <iostream>
#include <string>
#include <initializer_list>

template <typename T>
void func(T t) 
{
    std::cout << t << std::endl ;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    std::cout << t <<std::endl ;

    func(args...) ;
}

template <class T>
void func2( std::initializer_list<T> list )
{
    for( auto elem : list )
    {
        std::cout << elem << std::endl ;
    }
}

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    func(1,2.5,'a',str1);

    func2( {10, 20, 30, 40 }) ;
    func2( {str1, str2 } ) ;
} 

Если вы используете gccили clangмы можем использовать магическую переменную PRETTY_FUNCTION для отображения сигнатуры типа функции, которая может быть полезна для понимания происходящего. Например, используя:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

приведет к следующему int для функций с переменным числом в примере ( см. в прямом эфире ):

void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello

В Visual Studio вы можете использовать FUNCSIG .

Обновление до C ++ 11

До C ++ 11 альтернативой для std :: initializer_list был бы std :: vector или один из других стандартных контейнеров :

#include <iostream>
#include <string>
#include <vector>

template <class T>
void func1( std::vector<T> vec )
{
    for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
    {
        std::cout << *iter << std::endl ;
    }
}

int main()
{
    int arr1[] = {10, 20, 30, 40} ;
    std::string arr2[] = { "hello", "world" } ; 
    std::vector<int> v1( arr1, arr1+4 ) ;
    std::vector<std::string> v2( arr2, arr2+2 ) ;

    func1( v1 ) ;
    func1( v2 ) ;
}

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

#include <iostream>
#include <string>
#include <cstdarg>

void simple_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '\n';
        }
        ++fmt;
    }

    va_end(args);
}


int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    simple_printf("dddd", 10, 20, 30, 40 );
    simple_printf("ss", str1.c_str(), str2.c_str() ); 

    return 0 ;
} 

Использование функций с переменным числом аргументов также имеет ограничения в аргументах, которые вы можете передать, что подробно описано в проекте стандарта C ++ в разделе « 5.2.2 Вызов функции», параграф 7 :

Когда для данного аргумента нет параметра, аргумент передается таким образом, что принимающая функция может получить значение аргумента, вызвав va_arg (18.7). Стандартные преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) выполняются в выражении аргумента. После этих преобразований, если аргумент не имеет арифметику, перечисление, указатель, указатель на член или тип класса, программа становится некорректной. Если аргумент имеет тип класса не POD (раздел 9), поведение не определено. [...]


Является ли ваш typenameпротив classиспользования выше преднамеренным? Если так, пожалуйста, объясните.
Кевинарпе

1
@kevinarpe не преднамеренный, но это ничего не меняет.
Шафик Ягмур

Ваша первая ссылка, вероятно, должна быть на en.cppreference.com/w/cpp/language/variadic_arguments .
Алексей Романов

Можно ли сделать функцию, принимающую initializer_listрекурсив?
idclev 463035818

33

Решение C ++ 17: полная безопасность типов + приятный синтаксис вызова

Поскольку введение шаблонов переменных в C ++ 11 и выражений сворачивания в C ++ 17, можно определить функцию-шаблон, которая на сайте вызывающей стороны может вызываться так, как если бы она была функцией Varidic, но с преимуществами для :

  • быть строго типографским;
  • работать без информации о количестве аргументов во время выполнения или без использования аргумента "stop".

Вот пример для смешанных типов аргументов

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

И еще один с принудительным совпадением типов для всех аргументов:

#include <type_traits> // enable_if, conjuction

template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;

template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!");   // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                               // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                              ^

Больше информации:

  1. Шаблоны Variadic, также известные как пакет параметров Пакет параметров (начиная с C ++ 11) - cppreference.com .
  2. Fold выражения Fold выражения (начиная с C ++ 17) - cppreference.com .
  3. Смотрите полную демонстрацию программы на колиру.

13
однажды я надеюсь, что смогу прочитатьtemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian

1
@Eladian читал это как «Эта вещь включена, только если Headи Tail... одинаковы », где « то же самое » означает std::conjunction<std::is_same<Head, Tail>...>. Прочитайте это последнее определение как « Headтакое же, как все Tail...».
YSC

24

в C ++ 11 вы можете сделать:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

инициализатор списка FTW!


19

В C ++ 11 есть способ создания шаблонов переменных аргументов, который приводит к действительно элегантному и безопасному типу способу иметь функции переменных аргументов. Сам Бьярне приводит хороший пример printf с использованием шаблонов переменных аргументов в C ++ 11FAQ .

Лично я считаю это настолько элегантным, что даже не стал бы беспокоиться о функции с переменным аргументом в C ++, пока этот компилятор не будет поддерживать шаблоны переменных C ++ 11.


@donlan - Если вы используете C ++ 17, вы можете использовать выражения сворачивания, чтобы в некоторых случаях все было намного проще (подумайте творчески, вы можете использовать ,оператор с выражениями свертывания). Иначе я так не думаю.
Всевышний

15

В C ++ поддерживаются функции с переменным числом символов.

Однако большинство библиотек C ++ используют альтернативные идиомы, например, в то время как 'c' printfфункция принимает переменные аргументы, c++ coutобъект использует <<перегрузку, которая затрагивает безопасность типов и ADT (возможно, за счет простоты реализации).


Кроме того, кажется, что это работает только в случае такой функции, как печать, где у вас есть итерация функции с одним аргументом для каждого аргумента. В противном случае, вы просто инициализируете список и передаете список на конец std::initializer_lists... И это уже вносит огромную сложность в простую задачу.
Кристофер

13

Помимо varargs или перегрузки, вы можете собирать свои аргументы в std :: vector или другие контейнеры (например, std :: map). Что-то вроде этого:

template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);

Таким образом, вы получите безопасность типов, и логический смысл этих переменных аргументов будет очевиден.

Конечно, у этого подхода могут быть проблемы с производительностью, но вам не следует беспокоиться о них, если вы не уверены, что не можете заплатить цену. Это своего рода "Pythonic" подход к C ++ ...


6
Чище было бы не применять векторы. Вместо этого используйте аргумент шаблона, определяющий коллекцию в стиле STL, затем выполните итерацию по нему, используя методы начала и конца аргумента. Таким образом, вы можете использовать std :: vector <T>, std :: array <T, N> c ++ 11, std :: initializer_list <T> или даже создать свою собственную коллекцию.
Йенс Окерблом

3
@ JensÅkerblom Я согласен, но это тот выбор, который следует проанализировать для решения данной проблемы, чтобы избежать чрезмерного проектирования. Так как это вопрос подписи API, важно понять компромисс между максимальной гибкостью и ясностью намерения / удобства использования / обслуживания и т. Д.
Франческо

8

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


Я полагаю, что вы склонны к ошибкам - это очень, очень опасно? Особенно при работе с ненадежным вводом.
Кевин Лони

Да, но из-за проблем безопасности типа. Подумайте обо всех возможных проблемах, которые имеет обычный printf: строки формата, не соответствующие переданным аргументам, и тому подобное. printf использует ту же технику, кстати.
Дэйв Ван ден Эйнде

7

Не существует стандартного C ++ способа сделать это без использования varargs в стиле C ( ...).

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

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Все четыре вызова функций вызывают myfuncс различным количеством аргументов. Если ничего не указано, используются аргументы по умолчанию. Заметьте, однако, что вы можете опустить только конечные аргументы. Нет способа, например, опустить iи дать только j.


4

Возможно, вы хотите перегрузку или параметры по умолчанию - определите ту же функцию с параметрами по умолчанию:

void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
   // stuff
}

void doStuff( double std_termstator )
{
   // assume the user always wants '1' for the a param
   return doStuff( 1, std_termstator );
}

Это позволит вам вызывать метод одним из четырех разных вызовов:

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

... или вы можете искать соглашения о вызовах v_args из C.


2

Если вы знаете диапазон количества аргументов, которые будут предоставлены, вы всегда можете использовать перегрузку некоторых функций, например

f(int a)
    {int res=a; return res;}
f(int a, int b)
    {int res=a+b; return res;}

и так далее...


2

Использование вариадических шаблонов, пример для воспроизведения, console.logкак видно из JavaScript:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

Например, имя файла js_console.h:

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};

1

Как уже говорили другие, С-стиль varargs. Но вы также можете сделать нечто подобное с параметрами по умолчанию.


Не могли бы вы уточнить?
Фонд Моника иск

@QPaysTaxes: пример того, как сделать это с аргументами по умолчанию, смотрите в ответе Золтана.
Томас Падрон-Маккарти

0

Теперь это возможно ... используя boost any и шаблоны. В этом случае тип аргументов может быть смешанным

#include <boost/any.hpp>
#include <iostream>

#include <vector>
using boost::any_cast;

template <typename T, typename... Types> 
void Alert(T var1,Types... var2) 
{ 

    std::vector<boost::any> a(  {var1,var2...});

    for (int i = 0; i < a.size();i++)
    {

    if (a[i].type() == typeid(int))
    {
        std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(double))
    {
        std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(const char*))
    {
        std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
    }
    // etc
    }

} 


void main()
{
    Alert("something",0,0,0.3);
}

0

Объедините решения C и C ++ для семантически простого, производительного и наиболее динамичного варианта. Если вы облажались, попробуйте что-нибудь еще.

// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
  T * arr = new T[n];
  va_list ap;
  va_start(ap, n);
  for (size_t i = 0; i < n; i++)
    T[i] = va_arg(ap,T);
  return arr;
}

Пользователь пишет:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

Семантически это выглядит и ощущается в точности как функция с n аргументами. Под капотом вы можете распаковать его так или иначе.


-1

Мы также могли бы использовать initializer_list, если все аргументы являются константными и имеют одинаковый тип


-2
int fun(int n_args, ...) {
   int *p = &n_args; 
   int s = sizeof(int);
   p += s + s - 1;
   for(int i = 0; i < n_args; i++) {
     printf("A1 %d!\n", *p);
     p += 2;
   }
}

Обычная версия


1
И неопределенное поведение, которое не будет работать ни на чем, кроме x86.
SS Anne
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.