c++templatessfinaeoverload-resolution

How can I get the behavior of a plain `auto` return type when using "expression SFINAE"?


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();
}

Solution

  • 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).