What does ... (ellipsis) as one and only function parameter in a function prototype mean in C++?

I´ve came across a function declaration, like:

int vsa_d(...);

with ... as one and only parameter.

I know that with an ellipsis, we can refer to multiple objects, but to what does the ... refer to here?


I´ve found here https://en.cppreference.com/w/cpp/language/variadic_arguments under "Notes":

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.

So, it shall be used for anything like a "fallback overload" in "SFINAE".

What does that mean?

Upvotes: 4

Views: 2453

Answers (2)

Andreas DM
Andreas DM

Reputation: 11018

int vsa_d(...); // can take any number of arguments

Here, vsa_d can take any number of arguments.

So, it shall be used for anything like a "fallback overload" in "SFINAE".

What does that mean?

Example:

template <typename T>
struct has_f {
  template <typename U, typename = decltype(std::declval<U&>().f())>
  static std::true_type foo(U);

  static std::false_type foo(...);

  using type = typename decltype(foo(std::declval<T>()))::type;
};

struct a {
  void f(){}
};

Here foo has two overloads:

template <typename U, typename = decltype(std::declval<U&>().f())>
static std::true_type foo(U);

If the expression decltype(std::declval<U&>().f() is valid, then whatever we called has_f with has indeed a function f and this overload will be chosen.

Otherwise, the non-template member function will be chosen

static std::false_type foo(...);

Because it has the lowest priority.


Calling

std::cout << std::boolalpha << has_f<a>::type();

gives

true

Upvotes: 2

NicholasM
NicholasM

Reputation: 4673

The ... argument is used as a catch-all in some SFINAE constructions.

Here is an except from the top answer in a question about writing a type trait has_helloworld<T> that detects whether a type T has a member helloworld:

template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

It works in the following way: if typeof(&T::helloworld) exists and is a well-formed, then at the site test<T>(0), the constant 0 is converted to a pointer-to-member(-function) and that overload is selected. The size of the return type is one.

If typeof(&T::helloworld) does not exist, then that overload is not in the potential overload set, and the fallback test(...) is selected as the overload. The size of the return type is two.

The test(...) overload has the nice property that it is always the worst-matching, last-selected overload. This means it can serve as the "fallback default" in such constructions.

Upvotes: 6

Related Questions