Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic function without named argument

I noticed, that both GCC and MSVC are happy with the following code:

#include <iostream>
void foo(...);

int main()
{
    foo();
}

void foo(...)
{
    std::cout << "foo\n";
}

More specifically, code was run under GCC 6.2.0 and Visual Studio 2015.

I know that C requires at least one named parameter preceding the ellipsis, which allows to handle any number of arguments using specialized va_start, va_args, and va_end macros from <stdarg.h> (here <cstdarg>) header. Otherwise, it won't even compile.

Does C++ have some special treatment for "pure ellipsis" form or is it not suitable for fetching the arguments, i.e. it's allowed, but completely impractical?

like image 709
Grzegorz Szpetkowski Avatar asked Sep 23 '16 15:09

Grzegorz Szpetkowski


2 Answers

C++ Variadiac arguments are explained here. This syntax is supported in C++, but the arguments are not accessible:

In the C programming language, at least one named parameter must appear before the ellipsis parameter, so printz(...); is not valid.

In C++, this form is allowed even though the arguments passed to such function are not accessible, and is commonly used as the fallback overload in SFINAE, exploiting the lowest priority of the ellipsis conversion in overload resolution. This syntax for variadic arguments was introduced in 1987 C++ without the comma before the ellipsis. When C89 adopted function prototypes from C++, it replaced the syntax with one requiring the comma. For compatibility, C++98 accepts both C++-style f(int n...) and C-style f(int n, ...)

like image 176
paul-g Avatar answered Sep 28 '22 07:09

paul-g


In C++ it is allowed because even if there is no named parameter before, the ... will only be an inaccessible variadic argument.

In C, there is no overloads, and having a function that receives only ... can be a great source of runtime error. Inaccessible varargs is not useful is C.

In C++, it is currently use as a sink function for sfinae. Compilers will always choose other overload if possible before resolving a call to a function with variadic parameter. It is pratical for sfinae purpose:

template<typename F, typename... Ts>
struct is_callable {
private:

    template<typename T, typename... Args>
    static decltype(
        static_cast<void>(std::declval<T>()(std::declval<Args>()...)),
        std::true_type{}
    ) test(int);

    template<typename...>
    static std::false_type test(...); // not a template variadic, classic vararg here.

public:
    // Here, the compiler will try the first version of the function
    // Because '...' is not the preferred overload
    // If the return type expression don't yield to a type, the compiler
    // will have no choice but to pick the variadic one,
    // resulting in a std::false_type
    using type = decltype(test<F, Ts...>(0));
};
like image 25
Guillaume Racicot Avatar answered Sep 28 '22 08:09

Guillaume Racicot