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:
decltype
that worksThanks!
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.
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).
You cannot overload function declarations that differ only by return type. The function overloading is basically the compile time polymorphism.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With