Consider the following code.
struct Widget {
int& get();
};
template<typename X>
auto func_1(X& x) {
return x.get();
}
template<typename X>
auto func_2(X& x) -> decltype(x.get()) {
return x.get();
}
When called with an l-value of type Widget
, the function func_1
will be instantiated with return type int
where function func_2
will have return type int&
.
Also, there is a difference between func_1
and func_2
in that "expression SFINAE" is performed for func_2
. So, for types X
which don't have a .get()
member, func_2
will not participate in overload resolution.
My question is: how can we get the return type behavior of func_1
while still performing expression SFINAE?
The following func_3
seems to work in the cases I tested, but I feel there should be a simpler alternative. Also, I'm not sure if func_3
has exactly the same return type as func_1
in all cases.
template<typename X>
auto func_3(X& x) -> std::remove_cvref_t<std::decay_t<decltype(x.get())>> {
return x.get();
}
Just std::decay_t<decltype(x.get())>
is enough. Or std::remove_cvref_t<decltype(x.get())>
(the latter doesn't perform array/function to pointer decay).
Or, in C++23 you can use decltype(auto(x.get()))
.
But if you're wrapping a function, usually the plain decltype
is more correct (e.g. in your case, if get()
returns a reference, auto
forces a copy).