Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding overload changes which overload is selected

Tags:

c++

I'm trying to understand how the overload selection rules result in the following (unintuitive) behavior. When I have the following functions:

#include <iostream>

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

The void write(T data) overload (Overload 1) is selected. I think that makes sense to me: the candidates for overload selection are void write<T>(T) T = int and void write<T,U>(T&) T = int, U = <>. Both write(T) and write(T&) would be equally specialized, but Overload 2 has an empty parameter pack so Overload 1 is selected. However, if I add a third overload:

#include <iostream>

// Overload 0
void write(const int& data)
{
  std::cout << "Called write(const int& data)" << std::endl;
}

// Overload 1
template<class T>
void write(T data)
{
  std::cout << "Called write(T data)" << std::endl;
}

// Overload 2
template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

Then all of a sudden void write(T&& obj, U&&... objs) (Overload 2) is what gets called. Why does adding an overload that doesn't get selected change which overload actually does get selected?

If the only candidates were void write<T,U>(T&) T = int, U = <> and void write(const int&) I understand why void write<T,U>(T&) would be selected, so perhaps something about adding the extra overload prevents void write(T data) from participating on overload selection? If so why?

Since this seems to be compiler specific behavior this was observed on gcc 7.3.0.

Some more interesting behavior: If the functions are reordered such that the new overload is placed between the original two (i.e. Overload 1, then Overload 0, then Overload 2) then gcc rejects it with call of overloaded ‘write(int&)’ is ambiguous. If the functions are reordered such that the new overload is last (i.e. Overload 1, then Overload 2, then Overload 0) then write(const int& data) is selected.

like image 511
shay Avatar asked Sep 20 '18 15:09

shay


People also ask

How do you overload a function in C++?

You overload a function name f by declaring more than one function with the name f in the same scope. The declarations of f must differ from each other by the types and/or the number of arguments in the argument list.

When you call an overloaded function how compiler identified?

At compile time, the compiler chooses which overload to use based on the types and number of arguments passed in by the caller. If you call print(42.0) , then the void print(double d) function is invoked. If you call print("hello world") , then the void print(std::string) overload is invoked.

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.

What are the principles of function overloading?

Rules of Function Overloading in C++The functions must have the same name. The functions must have different types of parameters. The functions must have a different set of parameters. The functions must have a different sequence of parameters.


1 Answers

I think this is a GCC bug:

Overloads are:

  • Overload 0: write(const int&)
  • Overload 1: write(T) [T=int] -> write(int)
  • Overload 2: write(T&&,U&&...) [T=int&,U=[]] -> write(int&)

Overload 0 is a better match than overload 1 because overload 0 is not a template functions specialization.

Overload 1 is a better match than overload 2 because overload 1 is a function template more specialized than overload 2.

Overload 2 is a better match than overload 0 because the cv qualifier of the parameter type of overload 2 int& is a subset of the one of overload 0: const int&.

So the call is ambiguous as reported by Clang.


To simplify, the best viable function is evaluate here in 4 steps here, when comparing two functions:

  1. Check which is the best conversion sequence (all conversions are the identity conversion here), then if the conversion ranks are the same:
  2. Check if between two reference bindings one is better than the other, then if one is not a reference binding or the two bindings are not differentiable,
  3. Check if one of the function is a template specialization and the other is not, then if the two are template specializations
  4. Check if one of the specializations is more specialized than the other.
like image 80
Oliv Avatar answered Oct 22 '22 06:10

Oliv