Ah, that explains a lot.
One of the other headers must be including <locale>
,
which as you say defines template<typename Char> Char toupper(Char, const std::locale &)
.
The main reason I decided to ask if std::toupper<char>
made a difference is because of the message <unresolved overloaded function type>
,
which implies that std::toupper
is overloaded.
According to cppreference, if only <cctype>
is included then there should only be one std::toupper
defined, no other overloads, and thus the code should be unambiguous (which is why the code I first posted compiled for me and not for you).
I wanted to see if there was a template overload declared,
because that would (partly) explain why the compiler is having a problem resolving the overloads.
I wasn’t sure if std::toupper
from <locale>
was the issue,
or if the standard library implementation was declaring another overload somewhere,
but I suspected testing std::toupper<char>
would provide the answer.
I’ve since tried to compile the following:
// For std::cout, std::cin
#include <iostream>
// For std::string
#include <string>
// For std::transform
#include <algorithm>
// For std::toupper
#include <cctype>
// To test for errors
#include <locale>
int main()
{
std::string str("abcdefg");
std::transform(str.begin(), str.end(), str.begin(), std::toupper);
std::cout << str << '\n';
std::cin.get();
}
And I also get a number of errors (different messages because I’m not using GCC, but the same cause).
This implies that if both int std::toupper(int)
and template<typename Char> Char std::toupper(Char, const std::locale &)
are defined, the compiler can’t decide which std::toupper
to use.
The reason using static_cast<int(*)(int)>(std::toupper)
solves this problem is because only int std::toupper(int)
can be converted to int(*)(int)
,
whereas the templated version (template<typename Char> Char std::toupper(Char, const std::locale &)
) can’t be converted to int(*)(int)
,
so that removes the ambiguity.
The same is true of:
[](int c){ return std::toupper(c); }
[](char c){ return std::toupper(c); }
[](unsigned char c){ return std::toupper(c); }
char ctoupper(char c) { return std::toupper(c); }
They all remove the ambiguity that’s causing template resolution to fail with std::transform(str.begin(), str.end(), str.begin(), std::toupper);
.
The reason ::toupper
succeeds is because it’s the same as int std::toupper(int)
from <cctype>
,
whereas the std::toupper
from <locale>
doesn’t have an equivalent in the global namespace.
::toupper
exists in the global namespace because of the rules governing the <cxxx>
-style headers,
which are the C++ versions of the headers from C.
Specifically, <cctype>
is the C++ version of <ctype.h>
,
and the standard library requires that std::toupper
is defined in the std
namespace,
while also allowing (but not guaranteeing) that ::toupper
to be declared in the global namespace.
(The same is true of all headers and functions ‘borrowed’ from C.)
But the explanation is not over yet.
That explains why the others work, and half of why the compiler gets confused,
but there’s more to it than that.
The error itself happens simply because the way the standard library implementations define std::transform
means that the compiler can’t decide which template instantiation is better.
It’s probably not worth trying to figure out exactly why that is,
for reasons I will explain in a moment.
Now, the interesting part is that technically speaking, this error is completely avoidable.
It is entirely possible to define std::transform
in such a way that it would correctly deduce that it needs to use the std::toupper
from <cctype>
and not the one from <locale>
.
However, the standard library does not require this, and doing so is somewhat difficult,
so the authors of your compiler and my compiler have chosen not to bother.
Most likely they decided it was too much effort for something so minor.
Or possibly it is because the facilities to do so (<type_traits>
and decltype
) were only introduced in C++11 and std::transform
has been around since C++98.
Or maybe they just aren’t aware of the issue.
Whatever the reason, the only ways to change this are to:
- Convince the GCC team that they should modify
std::transform
to deduce the better toupper
- Convince the C++ standards committee that
std::transform
should be required to be correctly deduce the better toupper
Both of which are a lot of effort and unlikely to happen.
(And ultimately C++20 is introducing a better solution in the form of concepts and constraints.)