Is it possible to get the number of arguments on a variadic function?

Viewed 1266

I've been looking into how to declare functions or class members with a variable number of argument, and came across variadic functions, however I was wondering if there was some way to access the number of arguments pass to the function, without having to pass it directly as a first argument, as most of the documentation presents. I am also aware that I can use either variadic templates or std::initializer_list, but since I was looking to pass multiple arguments of the same type, those seem both too generic and/or with a convoluted syntax.

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

In these particular example, func loops over the input arguments until a negative value is found (in order to illustrate the issue and force a stop flag) while other_func requires the number of arguments to be passed as the first argument. Both these implementations seemed to me rather flawed and unsafe, is there a better way to approach this?

4 Answers

since I was looking to pass multiple arguments of the same type

That's exactly what std::initialiser_list<int> would give you.

You seem to be mistaken about variadic functions. The declaration int args... doesn't mean "some amount of int arguments", instead it means "one int named args, followed by any number of arguments of any type"

If you use C-style varargs then no, you can only parse the argument list one at a time.

If you have the option of c++11 then you could use a variadic template function instead and use the sizeof... operator to get the size of the argument pack.

template<typename ... Args>
void func(char * leading, Args const & ... args)
{
  /* sizeof...(Args) will give you the number of arguments */
} 

If you have C++17 available to you, this can all be done at compile time with a variadic non-type template argument and a fold expression: (Live Demo)

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

(and if you're using C++20, you can enforce compile-time computation with consteval instead of constexpr. Demo 2)


If you're stuck with C++11, then you can still do it at compile-time, but we'll need some more boilerplate (Live Demo 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;
}

No, there is no standard-conforming way to detect the number of arguments passed to a (C-style) variadic function. You would have to pass the number in an initial argument, or use some sort of terminator that could be recognized as the end of the sequence. It would be preferable to use C++ facilities for this.

Related