I am just looking at some code with the following rough outline.
#include <iostream>
template<typename T, typename F>
decltype(auto) do_something(T&& object, F f) {
return f(std::forward<T>(object));
}
struct object {
int x;
int& ref() { return x; }
int const& ref() const { return x; }
};
struct f {
template<typename T>
decltype(auto) operator()(T&& x) {
return x.ref();
}
};
int main(int argc, char** argv) {
auto & x = do_something(object{}, f{});
return 0;
}
so if i called like this
auto& do_something(object{}, f{);
Some questions around this are that if f
returns a reference and f
takes ownership of object
via move semantics are we not left with a lifetime issue?
What are the potential issues of returning by decltype(auto)
?
Or is this better
#include <iostream>
#include <type_traits>
struct object {
int x{};
int const& ref() const noexcept { return x; }
int& ref() noexcept { return x; }
};
struct functor {
template<typename T>
decltype(auto) operator()(T&& arg) {
return arg.ref();
}
};
template<typename T, typename F>
decltype(auto) apply(T&& o, F f) {
if constexpr(std::is_lvalue_reference_v<T>) {
return f(std::forward<T>(o));
}else {
auto result = f(std::forward<T>(o));
return result;
}
}
If
f
is a function, then it's not possible for it to return a valid reference to it in the first place, even without your wrapper.But your code doesn't take ownership of the object it's given, so it's fine.
There's a different, rather obscure failure scenario:
f
is a functor that moves the parameter into its member variable, then returns a reference to it. Then you'd get a dangling reference afterf
dies:Here,
A{}(object{}).foo()
is legal, butdo_something(object{}, A{}).foo()
is UB.The solution is to use
F &&f
(which is a good idea anyway, to avoid a copy). You should alsostd::forward<F>(f)
when calling it: