I'm trying to provide a wrapper around std::invoke
to do the work of deducing the function type even when the function is overloaded.
(I asked a related question yesterday for the variadic and method pointer version).
When the function has one argument this code (C++17) works as expected under normal overload conditions:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{
return std::invoke(func, std::move(arg));
}
Reducing the code bloat is obviously needed for more input arguments, but that's a separate problem.
If the user has overloads differing only by const/references, like so:
#include <iostream>
void Foo (int &)
{
std::cout << "(int &)" << std::endl;
}
void Foo (const int &)
{
std::cout << "(const int &)" << std::endl;
}
void Foo (int &&)
{
std::cout << "(int &&)" << std::endl;
}
int main()
{
int num;
Foo(num);
Invoke(&Foo, num);
std::cout << std::endl;
Foo(0);
Invoke(&Foo, 0);
}
Then Invoke
deduces the function incorrectly, with g++ output:
(int &)
(const int &)(int &&)
(const int &)
And clang++:
(int &)
(const int &)(int &&)
(int &&)
(Thanks to geza for pointing out that clang's outputs were different).
So Invoke
has undefined behaviour.
I suspect that metaprogramming would be the way to approach this problem. Regardless, is it possible to handle the type deduction correctly at the Invoke
site?
For each function template Invoke
, the template argument deduction (that must succeed for overload resolution to consider it) considers each Foo
to see whether it can deduce however many template parameters (here, two) for the one function parameter (func
) involved. The overall deduction can succeed only if exactly one Foo
matches (because otherwise there is no way to deduce S
). (This was more or less stated in the comments.)
The first (“by value”) Invoke
never survives: it can deduce from any of the Foo
s. Similarly, the second (“non-const
reference”) overload accepts the first two Foo
s. Note that these apply regardless of the other argument to Invoke
(for arg
)!
The third (const T&
) overload selects the corresponding Foo
overload and deduces T
=int
; the last does the same thing with the last overload (where T&&
is a normal rvalue reference), and therefore rejects lvalue arguments despite its universal reference (which deduces T
as int&
(or const int&
) in that case and conflicts with func
’s deduction).
If the argument for arg
is an rvalue (and, as usual, isn’t const), both plausible Invoke
overloads succeed at deduction, and the T&&
overload should win (because it binds an rvalue reference to an rvalue).
For the case from the comments:
template <typename U>
void Bar (U &&);
int main() {
int num;
Invoke<void>(&Bar, num);
}
No deduction takes place from &Bar
since a function template is involved, so T
is successfully deduced (as int
) in every case. Then, deduction happens again for each case to identify the Bar
specialization (if any) to use, deducing U
as fail, int&
, const int&
, and int&
respectively. The int&
cases are identical and plainly better, so the call is ambiguous.
So Clang is right here. (But there’s no “undefined behavior” here.)
I don’t have a general answer for you; since certain parameter types can accept multiple value-category/const-qualification pairs, it’s not going to be easy to emulate overload resolution correctly in all such cases. There have been proposals to reify overload sets in one way or another; you might consider one of the current techniques along those lines (like a generic lambda per target function name).
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