Function Overloading Based on Value vs. Const Reference
Asked Answered
K

6

34

Does declaring something like the following

void foo(int x)        { std::cout << "foo(int)"         << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }

ever make sense? How would the caller be able to differentiate between them? I've tried

foo(9);  // Compiler complains ambiguous call.

int x = 9;
foo(x);  // Also ambiguous.

const int &y = x;
foo(y);  // Also ambiguous.
Kermis answered 28/3, 2011 at 21:29 Comment(0)
K
13

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!)

Kurzawa answered 28/3, 2011 at 21:37 Comment(4)
This is not true, they're distinguishable, see @Inverse's answer and this answer of a duplicate question.Microsurgery
i tested using vs2012, but it still complains about ambiguous call, are you sure casting to const reference is working?Journalism
const int && is still indistinguishable from int for rvalues.Hanrahan
I think you should instead use std::move(4) to call the second one.Livelong
K
6

You could do this with a template:

template<typename T> void foo(T x) { ... }

Then you can call this template by value or by reference:

int x = 123;
foo<int>(x);  // by value
foo<int const&>(x);  // by refernce
Kingofarms answered 29/3, 2011 at 2:42 Comment(0)
O
3

How would the caller be able to differentiate between them?

It cannot be differentiated in this case. Both the overloaded functions have the same type of primitive data type as the argument. And taking by reference doesn't count for a different type.

Ovolo answered 28/3, 2011 at 21:34 Comment(1)
Again, false, see the above comment.Microsurgery
D
3

You can use static_cast to explicitly select the overload to be called:

#include <iostream>

void foo(int x)        { std::cout << "foo(int)"         << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }

int main()
{
  int x = 0;

  auto f1 = static_cast< void(*)(int) >(foo);
  f1(x);

  auto f2 = static_cast< void(*)(const int&) >(foo);  
  f2(x);

}

However, you should ask yourself why you provided those two overloads in the first place. Either you are fine with making a copy or you are not. Both at the same time? Why? Also making it necessary for the caller to explicitly select the overload defeats the purpse of function overloading. If you really want that consider to supply two functions instead:

void foo_copying(int x)        { std::cout << "foo(int)"         << std::endl; }
void foo_non_copying(const int &x) { std::cout << "foo(const int &)" << std::endl; }
Dealing answered 21/11, 2020 at 14:52 Comment(0)
V
1

Not in C++. Functional languages such as Erlang and Haskell get closer by allowing you to specify function overloads based on parameter value, but most imperative languages including C++ require overloading based on method signature; that is, the number and type of each parameter and the type of the return value.

The const keyword in the signature defines not the type of the parameter, but its mutability within the function; a "const" parameter will generate a compiler error if modified by the function or passed by reference to any function that doesn't also use const.

Vivianviviana answered 28/3, 2011 at 21:33 Comment(0)
B
1

The compiler can't. Both definitions of foo can be used for all 'variants' of int.

In the first foo, a copy of the int is made. Copying an int is always possible.

In the second foo, a reference to a const int is passed. Since any int can be cast to a const int, a reference to it can be passed as well.

Since both variants are valid in all cases, the compiler can't choose.

Things become different if you e.g. use the following definition:

void foo (int &x);

Now calling it with foo(9) will take the first alternative, since you can't pass 9 as a non-const int reference.

Another example, if you replace int by a class where the copy constructor is private, then the caller can't make a copy of the value, and the first foo-variant will not be used.

Babe answered 28/3, 2011 at 21:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.