Можно ли получить количество аргументов функции с переменным числом аргументов?

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

#include <cstdarg>

bool func(int args...) {
    va_list list;
    va_start(list, args);   
    int val = args;
    while(val >=0) {
        std::cout << val << std::endl;
        val = va_arg(list, int);
    }
    return true;
}

bool other_func(int c, int args...) {
    va_list list;
    va_start(list, args);   
    int val = args;
    for (int i = 0; i<c; i++) {
        std::cout << val << std::endl;
        val = va_arg(list, int);
    }
    return true;
}

int main(int argc, char const *argv[]) {
    func(2, 7, 47, -1, 23 /* ignored */);
    other_func(3 /* n of parameters */, 2, 7, 47);

    return 0;
}

В этом конкретном примере func перебирает входные аргументы до тех пор, пока не будет найдено отрицательное значение (чтобы проиллюстрировать проблему и установить флаг остановки), в то время как other_func требует, чтобы количество аргументов было передано в качестве первого аргумента. Обе эти реализации показались мне довольно ущербными и небезопасными, есть ли лучший способ подойти к этому?


person joaocandre    schedule 15.09.2020    source источник
comment
Можете ли вы использовать C++11 или более позднюю версию?   -  person AndyG    schedule 15.09.2020
comment
В современном C++ почти нет допустимых вариантов использования функций с переменным числом переменных. В вашем случае, в зависимости от требований, почему бы вам просто не использовать контейнер (список, вектор, массив...) или даже std::initialiser_list для передачи нескольких аргументов одного и того же типа?   -  person AlefSin    schedule 15.09.2020
comment
Не имеет отношения к вашему вопросу, но вы не вызываете va_end ни в одной из своих функций. Это была бы веская причина предпочесть подход C++.   -  person Stephen Newell    schedule 15.09.2020


Ответы (4)


так как я хотел передать несколько аргументов одного и того же типа

Это именно то, что std::initialiser_list<int> дал бы вам.

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

person Caleth    schedule 15.09.2020
comment
Да, кажется, я неправильно понял, что влечет за собой int args.... Моя основная проблема с initializer_lists заключается в том, что мне нужен инициализатор скобок для передачи переменного количества аргументов, но я думаю, что это более простой подход. - person joaocandre; 15.09.2020

Если вы используете varargs в стиле C, то нет, вы можете анализировать список аргументов только по одному.

Если у вас есть опция С++ 11, вы можете вместо этого использовать функцию шаблона с переменным числом аргументов и использовать оператор sizeof..., чтобы получить размер пакета аргументов.

template<typename ... Args>
void func(char * leading, Args const & ... args)
{
  /* sizeof...(Args) will give you the number of arguments */
} 
person SoronelHaetir    schedule 15.09.2020
comment
Обратите внимание, что это создает другую функцию для всех вызовов, которые имеют разное количество аргументов и/или разные типы. Это может быть полезно, пока функция не сохраняет и не использует состояние при последующих вызовах. - person doug; 15.09.2020
comment
Есть ли способ указать один тип для Args. Одна из основных проблем с универсальным шаблоном заключается в том, что он может вызвать неоднозначность, если функция имеет перегрузки с другим типом, но фиксированным количеством аргументов. - person joaocandre; 15.09.2020

Если у вас есть С++ 17, все это можно сделать во время компиляции с помощью вариативного аргумента шаблона, отличного от типа, и выражения свертывания: (Текущая демонстрация)

template<int... args>
constexpr bool func() {
    return ((args < 0) || ...);
}


int main() {
    static_assert(func<2, 7, 47, -1, 23>());
    static_assert(!func<1, 2, 3>());

    return 0;
}

(а если вы используете C++20, вы можете применить вычисления во время компиляции с помощью consteval вместо constexpr. Демонстрация 2)


Если вы застряли на C++11, вы все равно можете сделать это во время компиляции, но нам понадобится еще один шаблон (Текущая демонстрация 3)

#include <type_traits>

namespace detail
{
    template<bool...>
    struct disjunction;
    
    template<bool b>
    struct disjunction<b> : std::integral_constant<bool, b>
    {};
     
    template<bool left, bool... Bs>
    struct disjunction<left, Bs...> : std::conditional<left, disjunction<left>, disjunction<Bs...>>::type
    {};
}

template<int... args>
constexpr bool func() {
    static_assert(sizeof...(args) > 0, "Need to pass more than 1 integer");
    return detail::disjunction<(args < 0)...>::value;
}


int main() {
    static_assert(func<2, 7, 47, -1, 23>(), "There is one negative number");
    static_assert(!func<1, 2, 3>(), "There aren't any negative numbers");

    return 0;
}
person AndyG    schedule 15.09.2020

Нет, не существует стандартного способа определить количество аргументов, переданных функции с переменным числом аргументов (в стиле C). Вам нужно будет передать число в начальном аргументе или использовать какой-то терминатор, который можно распознать как конец последовательности. Для этого предпочтительнее использовать средства C++.

person Sneftel    schedule 15.09.2020
comment
По средствам С++ я предполагаю, что вы имеете в виду вариативные шаблоны и/или списки инициализаторов, верно. Я также не совсем уверен, в чем разница между вариативной функцией C и C++. - person joaocandre; 15.09.2020
comment
@joaocandre обычно вариационная функция C - это вариационная функция, а вариационная функция C++ - это шаблон вариативной функции - person Caleth; 15.09.2020