3

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?

  • What does that mean and for what is it meant for?

  • To what ... gets evaluated by the compiler?

  • Could the ellipsis be used also as a function argument, at the invocation of a function?


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?

  • 1
    It does the same as it does in C. In C++ there are better options so you shouldn't use it. – Jesper Juhl Feb 01 '20 at 17:38
  • 1
    @JesperJuhl I´ll cite something what `ve found to that topic: "*In the C programming language, at least one named parameter must appear before the ellipsis parameter, so printz(...); is not valid.*" - https://en.cppreference.com/w/cpp/language/variadic_arguments under "*Notes*". – RobertS supports Monica Cellio Feb 01 '20 at 17:45
  • @JesperJuhl After that citation, it isn´t valid in C to code in this way and that is also a reason why I explicitly asked the question for C++. Why do you are certain that this will be valid in C? – RobertS supports Monica Cellio Feb 01 '20 at 17:59
  • @JesperJuhl I made an C experiment and tried to compile it with gcc; the result: `error: ISO C requires a named argument before ‘...’` – RobertS supports Monica Cellio Feb 01 '20 at 18:24
  • @JesperJuhl It seems to be used for a C++-own error handling or proofing kind of thing: `and is commonly used as the fallback overload in SFINAE, exploiting the lowest priority of the ellipsis conversion in overload resolution. ` – RobertS supports Monica Cellio Feb 01 '20 at 18:29

2 Answers2

6

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.

NicholasM
  • 4,557
  • 1
  • 20
  • 47
  • I think it's worth noting `typeof` is a GCC extension, not standard, but `decltype` would work instead. – aschepler Feb 02 '20 at 15:00
2
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
Andreas DM
  • 10,685
  • 6
  • 35
  • 62