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

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

Ответ 1

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

Ответ 2

В С++ 11 у вас есть две новые опции, так как справочная страница Variadic functions в разделе "Альтернативы" гласит:

  • Шаблоны Variadic также могут использоваться для создания функций, которые принимают переменное число аргументы. Они часто являются лучшим выбором, поскольку они не налагают ограничений на типы аргументов, не выполняют интегральные и с плавающей запятой, и безопасны по типу. (начиная с С++ 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.

Обновление Pre С++ 11

Pre С++ 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 ) ;
}

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

#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 ;
} 

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Лично я считаю это настолько изящным, что я даже не буду беспокоиться о переменной переменной в С++, пока этот компилятор не поддержит шаблоны аргументов переменной С++ 11.

Ответ 5

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

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

Ответ 6

Помимо varargs или overloading, вы можете рассмотреть возможность объединения ваших аргументов в 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);

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

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

Ответ 7

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

Поскольку введение шаблонов переменных в С++ 11 и выражений сворачивания в С++ 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, также известные как пакет параметров Пакет параметров (начиная с С++ 11) - cppreference.com.
  2. Сложите выражения Сложите выражение (начиная с С++ 17) - cppreference.com.
  3. Смотрите полную демонстрацию программы на coliru.

Ответ 8

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

Ответ 9

Нет стандартного метода С++ для этого, не применяя 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.

Ответ 10

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

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.

Ответ 11

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

Ответ 12

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

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

и т.д.

Ответ 13

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;
   }
}

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

Ответ 14

Использование переменных шаблонов, пример для воспроизведения 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;
    }
};

Ответ 15

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

Ответ 16

Теперь это возможно... используя 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);
}