Avoid spelling out type in artificially amgibuous overloaded function call

72 views Asked by At

Minimal example program:

#include <vector>
void f(std::vector<int>)    {} // #1
void f(std::vector<void *>) {} // #2
int main() { f({ 1 }); }

It would intuitively make sense for this to be a valid program: the call using overload #1 would be valid, the call using overload #2 would be ill-formed, therefore overload #1 should be picked. This is what clang does.

Unfortunately, by the standard, it appears that this is ambiguous, because there is a constructor for std::vector<void *> that can be called with an int, by implicitly converting it to size_t. The fact that that constructor is explicit is supposed to be ignored during overload resolution, the program would simply be ill-formed if that overload were picked. GCC rejects the call as ambiguous and it looks like it is correct in doing so.

I can modify the code to let GCC accept the call by spelling out the type name: f(std::vector<int>{ 1 });. I can alternatively use tag dispatching with defaulted parameters to allow explicitly specifying which overload to use, while allowing existing calls to be accepted as before.

Both of those are acceptable but get fairly verbose fairly quickly when going back to the real code. Is there another option that allows me to avoid spelling out the complete type name, but stick with the current overloads? I was thinking for a moment { 1, } might work, but of course it doesn't, int i = { 1, }; is perfectly valid as well, that cannot be used to avoid #2.

If it helps to rule out some alternatives, the real code does involve std::vector<int> and std::vector<T> and does involve a call with a braced initialiser list containing a single integral expression, but T is a user-defined type, not a built-in type, and the expression is not a constant value.

"No" is an acceptable answer, but in that case, please elaborate, please show that there is no such option.

1

There are 1 answers

0
Davis Herring On

It’s always hard to prove a negative, but given the possibility of introducing

using I=std::vector<int>;

I think you’re really asking “is there a way to avoid the standard conversion to size_type to remove the other overload from contention”?

Obviously you can’t do anything about int itself, and even an implicit conversion to int can be followed by a standard conversion. But we can (of course) bring SFINAE to bear:

struct A {
  int i;
  template<class T,std::enable_if_t<std::is_same_v<T,int>>* =nullptr>
  operator T() const {return i;}
};

You can then write

f({A{1}});

(A user-defined literal would be sightly briefer, but you said non-constant expressions.)

It’s up to you whether this means yes or no for an answer, but I’m pretty sure there’s no other way of using a brace-initializer without allowing standard conversions.