This code prints Upper
, although I would want it to print Lower
:
#include <concepts>
#include <iostream>
struct X {
explicit operator bool() const { return true; }
};
template<class T>
requires std::constructible_from<T, X>
const T function() {
std::cout << "Upper";
return T();
}
template<class T>
T function() {
std::cout << "Middle";
return T();
}
template<>
bool function<bool>() {
std::cout << "Lower";
return true;
}
int main() {
function<bool>();
}
After all, while I see that Upper
works (as does Middle
), Lower
is a template specialization for bool
that should be preferred. If it is not using that overload, I wonder why the compiler does not fail with some kind of "ambiguity" error.
By trial-and-error, I have found that removing const
from the return value of Upper
seems to do the trick to call Lower
. Why?
Function templates can be overloaded and specialized. (unlike class templates, they cannot be partially specialized).
Overloading happens when there is more than one function template or function in play. Then overload resolution rules are used to pick which one is found when you call it.
Specialization only changes what after a template is selected. If a given template function has a (manual) specialization, its implementation is used.
In your case you have 2 overloads:
template<class T>
requires std::constructible_from<T, X>
const T function();
template<class T>
T function();
and one specialization of the 2nd overload:
template<>
bool function<bool>();
Given this, it is pretty clear what is going on; the compiler looks at those two overloads, see quite clearly the first is more-specialized (narrower), and selects the first overload. It never even considers the specialization you wrote: function template full specializations have zero impact on overload resolution.
(PS: returning const T
is code smell).
An obvious solution is:
template<class T>
requires std::constructible_from<T, X>
T function();
template<class T>
requires std::is_same_v<T, bool>
bool function();
template<class T>
T function();
which at least gets us this error message:
<source>:29:5: error: call to 'function' is ambiguous 29 | function<bool>(); [... details that point at the 2 requires templates as the ambiguous ones ...]
basically, neither is "more specialized".
We can force the compiler to understand one is narrower:
template<class T>
requires std::is_same_v<T, bool> && std::constructible_from<T, X>
bool function();
and the result works.
The other approach is to remove the const T
return value. Then
template<>
bool function<bool>()
becomes a valid specialization of both of the overloads. So while the first overload is selected, when the compiler looks for the implementation it uses the function<bool>
specialization.
I will note how fragile this is. I find the risks of using template specializations to be too high, as subtle things (like const
in your example) cause things to disconnect unexpectedly.
If I want to dispatch on a type, I'll change that type to a tag argument:
template<class T>
T function() { return function_impl( tag_v<T> ); }
then I can do everything at overload resolution time.
template<class T>
requires std::constructible_from<T, X>
T function_impl(tag_t<T>) {
std::cout << "Upper";
return T();
}
template<class T>
T function_impl(tag_t<T>) {
std::cout << "Middle";
return T();
}
inline bool function_impl(tag_t<bool>) {
std::cout << "Lower";
return true;
}
The result isn't as fragile, and I find reasoning about overload resolution order to be easier than "is this a valid specialization" rules.