Переменное количество аргументов в C ++? - PullRequest
237 голосов
/ 01 ноября 2009

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

Ответы [ 15 ]

340 голосов
/ 02 мая 2013

В 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

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

#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), массив-указатель (4.2) и функция-указатель (4.3) выполняются в выражении аргумента. После этих преобразований, если аргумент не имеет арифметику, перечисление, указатель, указатель на член или тип класса, программа становится некорректной. Если аргумент имеет тип класса не POD (раздел 9), поведение не определено. [...]

137 голосов
/ 01 ноября 2009

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

19 голосов
/ 17 мая 2017

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

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

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

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

17 голосов
/ 01 ноября 2009

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

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

15 голосов
/ 01 ноября 2009

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

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

13 голосов
/ 01 ноября 2009

Помимо 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 ++ ...

11 голосов
/ 22 марта 2018

Решение 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. Сложите выражения Сложите выражение (начиная с C ++ 17) - cppreference.com .
  3. См. полную демонстрацию программы на coliru.
8 голосов
/ 01 ноября 2009

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

7 голосов
/ 01 ноября 2009

Нет стандартного 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 голосов
/ 01 ноября 2009

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

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.

...