Function Overloading Based on Value VS. Const Reference

Function Overloading Based on Value vs. Const Reference

The intent seems to be to differenciate between invocations with temporaries (i.e. 9) and 'regular' argument passing. The first case may allow the function implementation to employ optimizations since it is clear that the arguments will be disposed afterwards (which is absolutely senseless for integer literals, but may make sense for user-defined objects).

However, the current C++ language standard does not offer a way to overload specifically for the 'l/r-valueness' of arguments - any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable.

C++11 introduces a new tool for a similar purpose — using r-value references, you can overload as follows

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

... and foo(4) (a temporary, r-value passed as argument) would cause the compiler to pick the second overload while int i = 2; foo(i) would pick the first.

(note: even with the new toolchain, it is not possible to differentiate between the cases 2 and 3 in your sample!)

Overloading reference vs const reference

Given two competing overloads, the standard requires the compiler to select the overload that has the "best fit". (If there's no unique best overload, or if the unique best overload is inaccessible, the program is ill-formed.)

In this case, the rules are provided by §13.3.3.2 [over.ics.rank]/p3:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if:

  • [...]

  • S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

This is the example given in the standard:

int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous

In your case, const T& is more cv-qualified than T&, so by the standard, f(T&) is a better fit than f(const T&) and is selected by overload resolution.

C++ function overloading resolution involving pass-by-value, reference and constant reference

All three calls are ambiguous, so the program won't compile.

f(3);

This could use either the first or third overload. They are equally good, so the call is ambiguous.

f(i);

This could use the first, second, or third overload. The second is better than the third; binding to int& is preferred over binding to const int& when it's possible. So it's fine to overload on cv-qualification in this way. But there is an ambiguity between the first and second overloads.

f(ci);

This could use the first or third overload. Again, they are equally good, so the call is ambiguous.


The standard precisely specifies the rules of overload resolution. They are very complicated, so it is a bad idea to overload functions in a way that makes it hard for the reader to tell which overload will be called. You can find the rules here.

Function overloading in C++ passing arguments by value or by reference

You may do a cast (of the function) to select the overload function:

static_cast<void (&)(int&)>(foo)(i);

Demo

Why the overloaded method with const reference return value is not called?

Overload resolution doesn't depend on return values, but only arguments, including the object to be called on for member function. a is a non-const object, then for a.get_ref(), the non-const member function will always be called.

You can cast it to const for the const version to be called:

const_cast<const A&>(a).get_ref();

BTW: Giving them different names is not a bad idea. That's why we have std::cbegin and std::cend in STL.

Write overloads for const reference and rvalue reference

My opinion is that understanding (truly) how std::move and std::forward work, together with what their similarities and their differences are is the key point to solve your doubts, so I suggest that you read my answer to What's the difference between std::move and std::forward, where I give a very good explanation of the two.


In

void foo(MyObject &&obj) {
globalVec.push_back(obj); // Moves (no, it doesn't!)
}

there's no move. obj is the name of a variable, and the overload of push_back which will be called is not the one which will steal reasources out of its argument.

You would have to write

void foo(MyObject&& obj) {
globalVec.push_back(std::move(obj)); // Moves
}

if you want to make the move possible, because std::move(obj) says look, I know this obj here is a local variable, but I guarantee you that I don't need it later, so you can treat it as a temporary: steal its guts if you need.

As regards the code duplication you see in

void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject&& /*rvalue reference -> std::move it */ obj) {
globalVec.push_back(std::move(obj)); // Moves (corrected)
}

what allows you to avoid it is std::forward, which you would use like this:

template<typename T>
void foo(T&& /* universal/forwarding reference -> std::forward it */ obj) {
globalVec.push_back(std::forward<T>(obj)); // moves conditionally
}

As regards the error messages of templates, be aware that there are ways to make things easier. for instance, you could use static_asserts at the beginning of the function to enfornce that T is a specific type. That would certainly make the errors more understandable. For instance:

#include <type_traits>
#include <vector>
std::vector<int> globalVec{1,2,3};

template<typename T>
void foo(T&& obj) {
static_assert(std::is_same_v<int, std::decay_t<T>>,
"\n\n*****\nNot an int, aaarg\n*****\n\n");
globalVec.push_back(std::forward<T>(obj));
}

int main() {
int x;
foo(x);
foo(3);
foo('c'); // errors at compile time with nice message
}

Then there's SFINAE, which is harder and I guess beyond the scope of this question and answer.

My suggestion

Don't be scared of templates and SFINAE! They do pay off :)

There's a beautiful library that leverages template metaprogramming and SFINAE heavily and successfully, but this is really off-topic :D

Overload resolution between object, rvalue reference, const reference

What are the rules here?

As there is only one parameter, the rule is that one of the three viable parameter initializations of that parameter must be a better match than both the other two. When two initializations are compared, either one is better than the other, or neither is better (they are indistinguishable).

Without special rules about direct reference binding, all three initializations mentioned would be indistinguishable (in all three comparisons).

The special rules about direct reference binding make int&& better than const int&, but neither is better or worse than int. Therefore there is no best match:

S1    S2
int int&& indistinguishable
int const int& indistinguishable
int&& const int& S1 better

int&& is better than const int& because of 13.3.3.2:

S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

But this rule does not apply when one of the initializations is not a reference binding.

Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy."

You propose to make a reference binding a better match than a non-reference binding. Why not post your idea to isocpp future proposals. SO is not the best for subjective discussion / opinion.

Is function overloading by reference allowed when there is no ambiguity?

Yes, it is allowed.

There is no rule to prevent this overload.

[C++14: 13.1/1]: Not all function declarations can be overloaded. Those that cannot be overloaded are specified here. [..]

[C++14: 13.1/2]: (blah blah lots of exceptions not including any for this case)

It would be extremely limiting for the language to prohibit function overloads that may be ambiguous in certain scenarios with certain calls, and for no good reason I might add!

Ambiguous function call when Reference and Value overload exist

It is ambiguous which function you intend to call, because any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable, as said in Function Overloading Based on Value vs. Const Reference.

You could change this:

void Add(T item) {}

to this:

void Add(T&& item) {}

Live demo

Function overloading in C++ passing arguments by value or by reference

You may do a cast (of the function) to select the overload function:

static_cast<void (&)(int&)>(foo)(i);

Demo



Related Topics



Leave a reply



Submit