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.