result_of<F()>::type is void ... why?

I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments. That leads to the following strangeness: struct foo { typedef int result_type; }; struct bar { template<class Sig> struct result { typedef int type; }; }; result_of<foo()>::type == int // 1 result_of<bar()>::type == void // 2 huh? result_of<foo(int)>::type == int // 3 result_of<bar(int)>::type == int // 4 This is the way it's specified in TR1, and boost::result_of implements this behavior faithfully, but I just can't understand why (2) is void instead of int. Can somebody explain it to me? -- Eric Niebler Boost Consulting www.boost-consulting.com

Eric Niebler wrote:
I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments. That leads to the following strangeness:
struct foo { typedef int result_type; };
struct bar { template<class Sig> struct result { typedef int type; }; };
result_of<foo()>::type == int // 1 result_of<bar()>::type == void // 2 huh?
result_of<foo(int)>::type == int // 3 result_of<bar(int)>::type == int // 4
This is the way it's specified in TR1, and boost::result_of implements this behavior faithfully, but I just can't understand why (2) is void instead of int. Can somebody explain it to me?
Ha! I've been wondering the same and figured the following reasons (but they aren't really good ones, IMO): There is no full specialization for member templates, so it's (seemingly) impossible to create a nested result<> metafunction (that's not really a problem since an unused extra template argument with a default allows us to define a partial specialization instead). If there was full specialization for member templates it wouldn't be lazy (IOW share its PoI with the surrounding class, just like operator()) which could be impractical in cases where the presence of a such a call operator depends on the type (== the template arguments) of the surrounding class (but 1. it's impractical anyway because operator() must be a class member and 2. there is no need to define a full specialization in the first place). I'd be interested to know better reasons, too. Regards, Tobias

Eric Niebler wrote:
I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments.
This special case is needed in situations such as: template<class F> struct X { F f_; result_of<F()>::type operator()() { return f_; } template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); } // ... }; where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in: X<int> x; The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge. There's no such problem with the A1 overload since, as a template, it's not instantiated unless used. With variadic templates a special case is no longer necessary and I believe that the C++0x result_of no longer has one.

Peter Dimov wrote:
Eric Niebler wrote:
I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments.
This special case is needed in situations such as:
template<class F> struct X { F f_;
result_of<F()>::type operator()() { return f_; }
template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); }
// ... };
where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in:
X<int> x;
The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge.
There's no such problem with the A1 overload since, as a template, it's not instantiated unless used.
With variadic templates a special case is no longer necessary and I believe that the C++0x result_of no longer has one.
Thanks. Will C++0x change the rules for operator overloading to be less restrictive (as the above problem would also be solved by a nullary, non-member call operator), BTW? Regards, Tobias

Peter Dimov wrote:
Eric Niebler wrote:
I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments.
This special case is needed in situations such as:
template<class F> struct X { F f_;
result_of<F()>::type operator()() { return f_; }
template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); }
// ... };
where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in:
X<int> x;
The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge.
Well, my reading of the spec makes the program ill-formed. F needs to be a class type for this odd rule to kick in. But ... yech. And the different behavior between foo and bar in my example is that we know how to test for member typedefs but not for member templates.
There's no such problem with the A1 overload since, as a template, it's not instantiated unless used.
With variadic templates a special case is no longer necessary and I believe that the C++0x result_of no longer has one.
I guess if the problem is going away, then that's something, I guess. Besides, the wording of the TR seems to allow me to force the issue, with: template<> struct result_of< bar() > : bar::result< bar() > {}; Ditto for the cv-qualifications of bar. I may end up doing that. Thanks, -- Eric Niebler Boost Consulting www.boost-consulting.com

On 5/21/07, Eric Niebler <eric@boost-consulting.com> wrote:
Peter Dimov wrote:
Eric Niebler wrote:
I've noticed something odd about the specification of tr1::result_of. In result_of<F()>::type, if F is a class type that doesn't have a nested result_type typedef, the result is "void". This is only true for F(); that is, when there are no arguments.
This special case is needed in situations such as:
template<class F> struct X { F f_;
result_of<F()>::type operator()() { return f_; }
template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); }
// ... };
where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in:
X<int> x;
The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge.
Well, my reading of the spec makes the program ill-formed. F needs to be a class type for this odd rule to kick in.
But ... yech. And the different behavior between foo and bar in my example is that we know how to test for member typedefs but not for member templates.
I agree with your reading. I think the above is ill-formed according to section 3.4/3. Incidentally, we do know how to test for member templates now. Here's a thread archive discussing my patch to MPL that does this: http://tinyurl.com/2os8fl. So, it would be possible to implement a result_of that deduces the correct return type in corner cases like your example without decltype. But decltype is a much better all around solution. Daniel

Daniel Walker wrote:
Incidentally, we do know how to test for member templates now. Here's a thread archive discussing my patch to MPL that does this: http://tinyurl.com/2os8fl.
So, it would be possible to implement a result_of that deduces the correct return type in corner cases like your example without decltype. But decltype is a much better all around solution.
Can you comment on why some of your tests fail with Comeau? I haven't looked into your patch, but if it doesn't work with Comeau, there's a good chance it's non-standard, which would be a deal-breaker for tr1::result_of. -- Eric Niebler Boost Consulting www.boost-consulting.com

On 5/21/07, Eric Niebler <eric@boost-consulting.com> wrote:
Daniel Walker wrote:
Incidentally, we do know how to test for member templates now. Here's a thread archive discussing my patch to MPL that does this: http://tinyurl.com/2os8fl.
So, it would be possible to implement a result_of that deduces the correct return type in corner cases like your example without decltype. But decltype is a much better all around solution.
Can you comment on why some of your tests fail with Comeau? I haven't looked into your patch, but if it doesn't work with Comeau, there's a good chance it's non-standard, which would be a deal-breaker for tr1::result_of.
I've been in touch with the Comeau folks, and they determined that the Comeau behavior that causes some tests to fail is non-standard. I believe they're working on fixing it for an upcoming release. So, Comeau will eventually pass all tests. Actually, this was a couple of months ago, and they may already have it fixed. I haven't gone back to check. But again, rather than changing the heuristic in TR1 to include a check for a result<> member, I prefer the recent amendment, which removes section 3.4/3 entirely since implementers can just use decltype. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2194.pdf Daniel

On May 21, 2007, at 7:49 AM, Peter Dimov wrote:
This special case is needed in situations such as:
template<class F> struct X { F f_;
result_of<F()>::type operator()() { return f_; }
template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); }
// ... };
where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in:
X<int> x;
The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge.
Yep, this is exactly the reason.
With variadic templates a special case is no longer necessary and I believe that the C++0x result_of no longer has one.
As soon as decltype gets in the language, result_of just "does the right thing" without this (or any) kludges. The LWG has accepted the appropriate change in principle, but of course it can't go in until decltype goes in. - Doug

Douglas Gregor wrote:
On May 21, 2007, at 7:49 AM, Peter Dimov wrote:
This special case is needed in situations such as:
template<class F> struct X { F f_;
result_of<F()>::type operator()() { return f_; }
template<class A1> result_of<F(A1)>::type operator()(A1& a1) { return f_(a1); }
// ... };
where X can be instantiated with non-nullary function objects, or with types that aren't function objects at all, as in:
X<int> x;
The above instantiates the declaration of X::operator()(), which attempts to instantiate result_of<int()>, which would fail without the kludge.
Yep, this is exactly the reason.
With variadic templates a special case is no longer necessary and I believe that the C++0x result_of no longer has one.
As soon as decltype gets in the language, result_of just "does the right thing" without this (or any) kludges. The LWG has accepted the appropriate change in principle, but of course it can't go in until decltype goes in.
Well, that last paragraph reads confusing to me. What is decltype(an_int()) or decltype(non_callable()), then? No compile error?! And isn't result_of working around a problem elsewhere in the language (namely the non-lazy instantiation of the nullary call operator), in fact making the switch to a decltype-based implementation harder? Regards, Tobias

On May 21, 2007, at 4:20 PM, Tobias Schwinger wrote:
Douglas Gregor wrote:
As soon as decltype gets in the language, result_of just "does the right thing" without this (or any) kludges. The LWG has accepted the appropriate change in principle, but of course it can't go in until decltype goes in.
Well, that last paragraph reads confusing to me.
What is decltype(an_int()) or decltype(non_callable()), then? No compile error?!
An error.
And isn't result_of working around a problem elsewhere in the language (namely the non-lazy instantiation of the nullary call operator), in fact making the switch to a decltype-based implementation harder?
As Peter noted, we'll also be using variadic templates, e.g., template<typename T> struct reference_wrapper { T* ptr; template<typename... Args> typename result_of<T(Args&&...)>::type operator()(Args&&... args); }; - Doug

Doug Gregor wrote:
On May 21, 2007, at 4:20 PM, Tobias Schwinger wrote:
What is decltype(an_int()) or decltype(non_callable()), then? No compile error?!
An error.
Thanks. The opposite would've surprised me...
And isn't result_of working around a problem elsewhere in the language (namely the non-lazy instantiation of the nullary call operator), in fact making the switch to a decltype-based implementation harder?
As Peter noted, we'll also be using variadic templates, e.g.,
template<typename T> struct reference_wrapper { T* ptr;
template<typename... Args> typename result_of<T(Args&&...)>::type operator()(Args&&... args); };
Wow! Those three features are certainly something to look forward to. I have to try your modified GCC, it seems... Regards, Tobias
participants (6)
-
Daniel Walker
-
Doug Gregor
-
Douglas Gregor
-
Eric Niebler
-
Peter Dimov
-
Tobias Schwinger