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?
(int &&)
twice for the second case. – PeculiarS
argument deduction. Try to comment out theconst T &
version ofInvoke
and notice the error. Also if the argument is provided explicitly (Invoke<void>(&Foo, num)
) the correct version is called. – Discriminateconst T&
, that's just avoiding the problem without solving it. – LibyanInvoke
, it can instantiate it with both the const and non-constFoo
. And it doesn't check that the return type (S
) is the same for both, so it says that it cannot deduceS
. So it ignores this template. While instantiating the constInvoke
can be done only with the constFoo
, so it can deduceS
in this case. Hence the compiler uses this template. – Peculiar&&
reference of the argument of theFoo()
function is a completely different kind of reference (a r-value reference) compared to the&&
for thearg
ofInvoke
(that is a reference of a template value, so is a forwarding reference). – BiyearlyInvoke
doesn't take that into account? – Libyan&&
version ofInvoke()
)std::move()
instead ofstd::forward
; second, because seems to me (maybe I'm wrong) that your code is based on the idea that aT && arg
is ever aT &&
(in a template function). – Biyearlystd::move
, my understanding was (correct or not) that anyBar(T&&)
input function would be deduced as aBar (T&)
if the input was an lvalue. If you replace the main with the following code, it behaves like I expected in g++, but don't compile in clang++:template <typename T> void Bar (T && t) { Foo(std::forward<T>(t)); } int main() { int num; Bar(num); Invoke<void>(&Bar, num); }
– LibyanBar(T&&)
input function would be deduced as aBar (T&)
if the input was an lvalue". So if you usestd::move()
over a l-value, you (potentially) make a disaster: inside the function you can depredate the function when externally is expected that remain valid. – BiyearlyBar(T&&)
is deduced toBar(int&)
, say, because of an int lvalue call, I thought that when you do theint x; Invoke(&Bar, x)
that it would callInvoke (FunctionType<S, T&>, T&)
. That's what happens with g++ (using my code from my previous comment, and putting a print in the invokes), but sadly this causing a compile error for clang++. – Libyan