Reputation: 15062
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?
Upvotes: 4
Views: 2453
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
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