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. Orstd::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
decltypeis more correct (e.g. in your case, ifget()returns a reference,autoforces a copy).