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.
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.
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.
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.
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.
I think this is a GCC bug:
Overloads are:
write(const int&)
write(T) [T=int] -> write(int)
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With