Consider:
void f(int); // (1)
void f(int, int); // (2)
auto pf1 = static_cast< void (*)(int) >(f); // Ok, address of (1)
auto pf2 = static_cast< void (*)(int, int) >(f); // Ok, address of (2)
static_assert(std::invocable<decltype(pf1), int>); // passed
static_assert(std::invocable<decltype(pf2), int, int>); // passed
static_assert(!std::invocable<decltype(pf2), int>); // passed
Now, instead of explicit cast, I would like to use concepts to help the compiler doing overload resolution:
std::invocable<int> auto pf1 = f; // Only (1) satisfies, there is no ambiguity
Except it doesn't compile. My question is why?
If the example was to work, we would be able to write:
std::string s;
std::ranges::transform(s, s.begin(), std::toupper);
The only effect that a type constraint on a variable declaration currently has is to implicitly
static_assertthat the type, deduced as if there was no type constraint, satisfies the constraints. It isn't specified to affect overload resolution or type deduction in any way.It wouldn't help for standard library functions like
touppereither way, because most of them are not designated as addressable, meaning that you don't know what overload structure is used to implement them and so it is not guaranteed that you are able to take a pointer/reference to them in any situation. For examplestd::touppermight be implemented as a single function template instead of multiple overloads. Then simply checking the constraint is not enough to make what you want work. You would need to somehow make template argument deduction choose the set of template arguments that happen to make the function with anint. In general that is an undecidable problem.The only reliable way to pass
toupperto a function is by wrapping it in a lambda. An explicit cast or::toupperis also not guaranteed to work. Additionally, you want that lambda anyway, because as mentioned in the question comments, just callingtoupperdirectly will cause undefined behavior for certain inputs.So, I have doubts that you would be able to come up with reasonable rules that could be added to the language to actually achieve the behavior that you want.
I think it would be possible to add something that says that overloads of
fare dropped instd::invocable<int> auto pf1 = f;if the overload (after template argument deduction) doesn't satisfy the type constraint. But that wouldn't help iffis implemented as template instead of multiple overloads and it also won't work for function parameters of the same form because type constraints on a function template are not associated with a single function parameter, but the whole function template.