decltype, LVALUE_TYPEOF, and binding a reference

Hi all. The primary purpose of decltype, according to the decltype/auto proposal, seems to be to assist in creation forwarding functions, something like: template<class F> decltype(F()()) forward(F f) { return f(); } For example, if f is declared to return const rvalue: const x f(); using the real decltype (in the future, of course) would result in: const x forward(const x(*)()) { return f(); } Which is, of course, what we expect in this case. Since LVALUE_TYPEOF has a problem distinguishing const x from const x&, using it in the above case would result in: const x& forward(const x(*)()) { ^^^^^^^ return f(); } Now, my question is: isn't this fine? The same rule (extending the lifetime of const reference) that prevented LVALUE_TYPEOF from working correctly, seems to now ensure that it is fine to use this "incorrect" result. Another example of (it seems) the same thing is the treatment of "volatile" by Microsoft compilers (both 7.1 and 8.0). It seems to be fine to write: volatile x vf(); volatile x& vr = vf(); provided x is a UDT, and has a copy-constructor accepting [const] volatile x&. (I don't know if this is a compiler bug, or the lifetime is also extended. I also don't know if this standard-compliant.) Needless to say, LVALUE_TYPEOF(vf()) yields volatile x& instead of expected volatile x (my local version, after adding the treatment for volatiles). If x is not a UDT, the above binding doesn't work, but LVALUE_TYPEOF(vf()), guided by the same exact mechanism, would also yield just x. Let's generalize a bit. When writing a forwarding function, shouldn't we, instead of asking: what is the exact return type of the wrapped function? just ask: what is the type to which the result of the wrapped function can be bound without loosing functionality [on this particular compiler]? It seems to me that using the simple mechanism of binding a reference yelds the results that are at least as good (for this particulat purpose, of course) as those that the real decltype could provide... But maybe I am missing something? Regards, Arkadiy

On Feb 11, 2006, at 8:27 AM, Arkadiy Vertleyb wrote:
Hi all.
The primary purpose of decltype, according to the decltype/auto proposal, seems to be to assist in creation forwarding functions, something like:
template<class F> decltype(F()()) forward(F f) { return f(); }
For example, if f is declared to return const rvalue:
const x f();
using the real decltype (in the future, of course) would result in:
const x forward(const x(*)()) { return f(); }
Which is, of course, what we expect in this case.
Since LVALUE_TYPEOF has a problem distinguishing const x from const x&, using it in the above case would result in:
const x& forward(const x(*)()) { ^^^^^^^ return f(); }
Now, my question is: isn't this fine? The same rule (extending the lifetime of const reference) that prevented LVALUE_TYPEOF from working correctly, seems to now ensure that it is fine to use this "incorrect" result.
I think the extended lifetime rule for temporaries would make it work ok, but the mere definition of the "forward" function above will at least trigger warnings about returning a reference to a temporary. (I'm assuming you meant "const x(*f)()" as the parameter of the forward function) Best, Jaakko

Hi Jaakko,
I think the extended lifetime rule for temporaries would make it work ok, but the mere definition of the "forward" function above will at least trigger warnings about returning a reference to a temporary.
(I'm assuming you meant "const x(*f)()" as the parameter of the forward function)
Right. Should have verified it myself rather than just speculating :-( However, this problem seems to be easily remedied with assigning to an intermediate variable. The following compiles w/o warnings, and runs correctly, on mingw 3.4.2, and VC7.1/8.0 (warning level 4): #include <iostream> using namespace std; const int f() { return 0; } const int& ff() { const int& result = f(); return result; } int main() { const int& r = ff(); cout << r << endl; return 0; } I am still not sure though, if the lifetime is extended far enough for this case, or does this just work correctly by accident... (have to take a closer look into the Standard) Regards, Arkadiy

Vertleyb wrote:
#include <iostream> using namespace std;
const int f() { return 0; } const int& ff() { const int& result = f(); return result; } int main() { const int& r = ff(); cout << r << endl; return 0; }
I am still not sure though, if the lifetime is extended far enough for this case, or does this just work correctly by accident... (have to take a closer look into the Standard)
This is broken. f() returns a temporary, which is bound to a const ref and has its lifetime extended to the end of the scope containing the const ref. So when ff() returns, the temporary object will be cleaned up, so ff() is returning a dead reference. If you use an object with a destructor instead of int, you can set a breakpoint to verify. -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> wrote
Vertleyb wrote:
I am still not sure though, if the lifetime is extended far enough for this case, or does this just work correctly by accident... (have to take a closer look into the Standard)
This is broken. f() returns a temporary, which is bound to a const ref and has its lifetime extended to the end of the scope containing the const ref. So when ff() returns, the temporary object will be cleaned up, so ff() is returning a dead reference. If you use an object with a destructor instead of int, you can set a breakpoint to verify.
I see... So, this makes my original assumption wrong :-( Any luck with telling const x from const x&? Regards, Arkadiy

"Eric Niebler" <eric@boost-consulting.com> wrote
Arkadiy Vertleyb wrote:
Any luck with telling const x from const x&?
I can on VC7.1, VC8 and gcc 3.4+ and 4.x, but I'm pretty sure it's not standard.
What about volatile/volatile& and volatile const/volatile const& in VC7.1, VC8, for UDT? Looks like they apply this lifetime extension rule to them, too... Regards, Arkadiy
participants (4)
-
Arkadiy Vertleyb
-
Eric Niebler
-
Jaakko Järvi
-
Vertleyb