Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does auto return type change the overload resolution?

Tags:

Thanks to decltype as a return type, C++11 made it extremely easy to introduce decorators. For instance, consider this class:

struct base {   void fun(unsigned) {} }; 

I want to decorate it with additional features, and since I will do that several times with different kinds of decorations, I first introduce a decorator class that simply forwards everything to base. In the real code, this is done via std::shared_ptr so that I can remove decorations and recover the "naked" object, and everything is templated.

#include <utility> // std::forward struct decorator {   base b;    template <typename... Args>   auto   fun(Args&&... args)     -> decltype(b.fun(std::forward<Args>(args)...))   {     return b.fun(std::forward<Args>(args)...);   } }; 

Perfect forwarding and decltype are just wonderful. In the real code, I actually use a macro that just needs the name of the function, all the rest is boilerplate.

And then, I can introduce a derived class that adds features to my object (derived is improper, agreed, but it helps understanding that derived is-a-kind of base, albeit not via inheritance).

struct foo_t {}; struct derived : decorator {   using decorator::fun; // I want "native" fun, and decorated fun.   void fun(const foo_t&) {} };  int main() {   derived d;   d.fun(foo_t{}); } 

Then C++14 came, with return type deduction, which allows to write things in a simpler way: remove the decltype part of the forwarding function:

struct decorator {   base b;    template <typename... Args>   auto   fun(Args&&... args)   {     return b.fun(std::forward<Args>(args)...);   } }; 

And then it breaks. Yes, at least according to both GCC and Clang, this:

  template <typename... Args>   auto   fun(Args&&... args)     -> decltype(b.fun(std::forward<Args>(args)...))   {     return b.fun(std::forward<Args>(args)...);   } }; 

is not equivalent to this (and the issue is not auto vs. decltype(auto)):

  template <typename... Args>   auto   fun(Args&&... args)   {     return b.fun(std::forward<Args>(args)...);   } }; 

The overload resolution seems to be completely different and it ends like this:

clang++-mp-3.5 -std=c++1y main.cc main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int'     return b.fun(std::forward<Args>(args)...);                  ^~~~~~~~~~~~~~~~~~~~~~~~ main.cc:32:5: note: in instantiation of function template specialization       'decorator::fun<foo_t>' requested here   d.fun(foo_t{});     ^ main.cc:7:20: note: passing argument to parameter here   void fun(unsigned) {}                    ^ 

I understand the failure: my call (d.fun(foo_t{})) does not match perfectly with the signature of derived::fun, which takes a const foo_t&, so the very eager decorator::fun kicks in (we know how Args&&... is extremely impatient to bind to anything that does not match perfectly). So it forwards this to base::fun which can't deal with foo_t.

If I change derived::fun to take a foo_t instead of const foo_t&, then it works as expected, which shows that indeed here the problem is that there is a competition between derived::fun and decorator::fun.

However why the heck does this show with return-type deduction??? And more precisely why was this behavior chosen by the committee?

To make things easier, on Coliru:

  • the version with decltype that works
  • the version with type deduction that fails

Thanks!

like image 472
akim Avatar asked Nov 03 '14 08:11

akim


People also ask

Why we Cannot overload function on the basis of return type?

The return type of a function has no effect on function overloading, therefore the same function signature with different return type will not be overloaded. Example: if there are two functions: int sum() and float sum(), these two will generate a compile-time error as function overloading is not possible here.

Can you overload by return type?

No, you cannot overload a method based on different return type but same argument type and number in java. same name. different parameters (different type or, different number or both).

Can you overload a function based on return type C++?

You cannot overload function declarations that differ only by return type. The function overloading is basically the compile time polymorphism.

What is overload resolution?

The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.


1 Answers

Just look at this call:

d.fun(foo_t{}); 

You create a temporary (i.e rvalue), passing it as argument to the function. Now what do you think happens?

  • It first attempts to bind to the argument Arg&&, as it can accept rvalue but due to invalid return type deduction (which again due to foo_t cannot convert into unsigned int, because of which b.fun(std::forward<Args>(args)...) turns out to be invalid expression), this function is rejected if you use the decltype(expr) as return type, as in this case SFINAE comes into picture. But if you use simply auto, then SFINAE doesn't come into picture and the error is classified to be a hard-error which results in compilation failure.

  • The second overload which accepts foo_t const& as argument is called if SFINAE works in the first case.

like image 160
Nawaz Avatar answered Oct 13 '22 14:10

Nawaz