foo() precedence over foo(...) for foo() call?

I thought that I could rely on the fact that when foo() and foo(...) were available a call to foo() would be unambiguous because foo() would take precedence over foo(...). I used that in the following code, which worked perfectly on VC++ but fails to compile on GCC. I find it strange that this would be implementation dependant !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>

template<typename T, typename = decltype(std::declval<T>()[int()])>
constexpr bool  _is_random() { return true; }

template<typename T>
constexpr bool _is_random(...) { return false; }

template<typename T>
constexpr bool is_random = _is_random<T>();

int main()
{
	std::vector<int> v = { 1,2,3,4,5,6 };
	std::cout << "-> " << is_random< std::vector<int>>;
}


I know a work around which is to define foo(int) and foo(double), and make the call to foo(1).
Last edited on
Is '_is_random(...)' correct? I thought variadic functions needed type.
According to this https://en.cppreference.com/w/cpp/language/variadic_arguments
_is_random(...)' is formally correct but the arguments cannot be retrieved.
The error code I get is about ambiguity of signature.

And by the way this needs to be compiled under c++17 because of the templated variable.

And the work around is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>

template<typename T, typename = decltype(std::declval<T>()[int()])>
constexpr bool  _is_random(int) { return true; }

template<typename T>
constexpr bool _is_random(double) { return false; }

template<typename T>
constexpr bool is_random = _is_random<T>(1);

int main()
{
	std::vector<int> v = { 1,2,3,4,5,6 };
	std::cout << "-> " << is_random< std::vector<int>>;
}
Last edited on
_is_random(...) gets converted to a regular function right.. So maybe that's what's making it ambiguous because there are two functions that eventually become the same thing (since you passed only one argument).

I wonder how VS takes care of this.
And by the way this needs to be compiled under c++17 because of the templated variable.

Variable templates are C++14 - just MSVC doesn't like them.

I'm pretty sure GCC is correct:
https://timsong-cpp.github.io/cppwp/n4659/over.match.viable#2
Both candidates are viable, and there are no parameters that overload resolution can use to compare conversion sequences:
https://timsong-cpp.github.io/cppwp/n4659/over.match.best#1

In the wild, I see alternatively
f(int) paired with f(long), or f(int) paired with f(...).
I use the latter, because the ellipsis always marks the least preferable choice.
Last edited on
Grime wrote:
_is_random(...) gets converted to a regular function right.. So maybe that's what's making it ambiguous because there are two functions that eventually become the same thing (since you passed only one argument).


The template stuff is extraneous here.

The original problem is the same as exhibited here:
1
2
3
4
void f() {}
void f(...) {}

int main() { f(); }

In this case, the ellipsis is not a template parameter pack. This is how C does variadic functions. For example, printf() has the signature
 
int printf(char const* fmt, ...);

It's a great exercise to implement, if you're so inclined....
The template stuff is extraneous here.

When you write a call to void f(...) {}, a function that accepts as many arguments as you send gets instantiated right? So void f(...) {} would instantiate an instance that accepts one integer argument if you were to call it with one integer.

Because types can't be interpreted run-time.

So if you wrote two calls (w/ different number of arguments), two functions would get instantiated with so and so number of parameters).

If I'm wrong correct me please.

And so if what I wrote is correct, how does Visual Studios take care of this? Does it not make an instance for void f(...) {} that takes integer argument when there is another function void f(int a) {} and when there's a function call to f(5); at all?
Last edited on
@Grime, since it's not a template there is no "instantiation". It's a single function. Apparently it's some kind of degenerate C-style variadic function, one whose parameters can't be accessed in the normal way (i.e., with the macros supplied in stdarg.h). I assume it would need some inline assembly?
Last edited on
When you write a call to void f(...) {}, a function that accepts as many parameters as you send gets instantiated right?

No: this is a run-time mechanism. In fact, f() might live in a library, where its source code is not available to the compiler. The compiler needs to see that source code if it plans to write a different function for each number of arguments.

Because of this, only the types already known to the implementation can be retrieved from inside a varargs function: printf() cannot handle std::string, for instance, because it does not know what it is - much less any user-defined type.

Additionally, this is a C feature, and C does not support function overloading.

In order for a varargs function to retrieve the arguments passed to it, the caller has to place the arguments in a known order in a known place (depending on the ABI). In addition, the callee has to know the types of arguments passed to it. For printf, for example, this information is encoded in the format string: %d means int, for example - and va_arg knows where to look in order to find the next int argument.

@dutch
In OP's case we don't care about the arguments - the ellipsis is only there as a C++ meta-programming trick used to manipulate overload resolution. But yeah, it would need ABI-specific inline assembly.
Last edited on
Okay, thanks mbozzi!
Thanks for engaging on this. The VC++ way of handling this seemed logical to me, f() being more specific than f(...) but it is what it is. I usually stay away from variadic functions anyway. It is just that I found the pairing f(...)/f() more pleasing visually than f(double)/f(int).
I think f(double) and f(int) can confuse the compiler as well, for a few edge cases where the type from an operation is neither but could be cast to both.
Topic archived. No new replies allowed.