I had a similar question, but specific to how to properly return ret as if we called invoke directly instead of call.
In the example you show, call(A, B) does not have the same return type of std::invoke(A, B) for every A and B.
Specifically, when invoke returns an T&&, call returns a T&.
You can see it in this example (wandbox link)
#include <type_traits>
#include <iostream>
struct PlainReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
return ret;
}
};
struct ForwardReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
return std::forward<decltype(ret)>(ret);
}
};
struct IfConstexprReturn {
template<class F, class Arg>
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
if constexpr(std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret);
} else {
return ret;
}
}
};
template<class Impl>
void test_impl(Impl impl) {
static_assert(std::is_same_v<int, decltype(impl([](int) -> int {return 1;}, 1))>, "Should return int if F returns int");
int i = 1;
static_assert(std::is_same_v<int&, decltype(impl([](int& i) -> int& {return i;}, i))>, "Should return int& if F returns int&");
static_assert(std::is_same_v<int&&, decltype(impl([](int&& i) -> int&& { return std::move(i);}, 1))>, "Should return int&& if F returns int&&");
}
int main() {
test_impl(PlainReturn{}); // Third assert fails: returns int& instead
test_impl(ForwardReturn{}); // First assert fails: returns int& instead
test_impl(IfConstexprReturn{}); // Ok
}
So it appears that the only way to properly forward the return value of a function is by doing
decltype(auto) operator()(F&& f, Arg&& arg) {
decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
if constexpr(std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret);
} else {
return ret;
}
}
This is quite a pitfall (which I discovered by falling into it!).
Functions which return T&& are rare enough that this can easily go undetected for a while.