Can you (and should you) disambiguate a function call taking T and const reference to T?

Viewed 153

If we have:

void foo(int) {}
void foo(const int&) {}

we cannot call foo like this:

foo(3);

because the call is ambiguous:

error: call of overloaded 'foo(int)' is ambiguous
40 |     foo(3);
   |          ^
note: candidate: 'void foo(int)'
36 | void foo(int) {}
   |      ^~~
note: candidate: 'void foo(const int&)'
37 | void foo(const int&) {}
   |      ^~~

What we can do is explicitly provide the correct overload, for example via a function pointer:

auto (*ptr)(const int&) -> void = foo;
ptr(3); // calls the "const int&" overload, obviously

However, that kind of defeats the purpose of convenient overloads. The question is - can I somehow disambiguate the call in a more... elegant? way? Are there ever cases where it would be desired to provide both overloads, for T and for const T&?

3 Answers

You can exploit templates. Overload resolution favours a non-template function over a template one, so converting one of the overloads to a template is a sufficent disambiguation:

#include <iostream>

void foo(int n)
{
    std::cout << "By Value  " << n;
}

template<int N = 0>
void foo(const int& n)
{
    std::cout << "By Reference " << n;
}

int main() {
    foo(1);
    foo<>(1);
}

Granted, you need <> to call the template one, but this could have some uses. Ostensibly more elegant than a function pointer? But alas it's not really much better than renaming say foo<> to bar.

It seems like you're asking whether we can force the overload resolution mechanism to select one signature over the other, rather than explicitly spelling out the signature that you want.

As far as I know, the only way to force overload resolution to pick the int overload over the const int& overload is to cast the argument to a volatile int glvalue, and there is no way to force overload resolution to pick the const int& overload over the int overload.

In any case, I can't think of any reason why one would want to write this particular set of overloads.

we cannot call foo like this: foo(3); because the call is ambiguous

In general, that's exactly why developers avoid providing overloads that similar. That makes it hard to use the overloads. Generally developers will have a const T& and a T&& overload, since those are never ambiguous.

However, that kind of defeats the purpose of convenient overloads.

The purpose of convenient overloads is to allow the calling code to easily call the right function. Calling exactly one is fulfilling the purpose of convenient overloads. The annoyance of casting to a function pointer is a side effect of having overloads too close togeather and is not normal.

The only exception I can think of is when you want to pass an overloaded set of functions to something else as a functoid, and let that caller pick which overload it wants.

struct foo_functoid {
    void operator()(int v) {foo(v);}
    void operator()(const int& v) {foo(v);}
};
//or
struct foo_functoid {
    template<class T>
    void operator()(T&& v) {foo(std::forward<T>(v));}
};

But unfortunately, for these functoids, there is no shortcut, they must be written explicitly by hand (with possibly minor assistance from macros)

Related