[tr1/result_of] SFINAE propagation by design?

Hi Doug and Boost community, I noticed that the Boost implementation of result_of uses inheritance to forward the type member of a nested result metafunction, so that result_of<...>::type can result in a substitution failure (and not an error when used in an appropriate context) if the nested result class template does not have a type member. It's surely a nice feature (also because it usually stops trace diagnostics at a reasonable point in case of an error). Should the standard text perhaps encforce that kind of implementation - e.g: [tr.func.ret.ret] 2 5 (b) If N>0, result_of<F(T1, T2, ... TN)> inherits from F::result<F(T1, T2, ... TN)> ? Comments? Regards, Tobias

Hi Tobias, On Dec 27, 2006, at 7:51 AM, Tobias Schwinger wrote:
Hi Doug and Boost community,
I noticed that the Boost implementation of result_of uses inheritance to forward the type member of a nested result metafunction, so that result_of<...>::type can result in a substitution failure (and not an error when used in an appropriate context) if the nested result class template does not have a type member.
It's surely a nice feature (also because it usually stops trace diagnostics at a reasonable point in case of an error). Should the standard text perhaps encforce that kind of implementation - e.g:
[tr.func.ret.ret] 2 5 (b)
If N>0, result_of<F(T1, T2, ... TN)> inherits from F::result<F(T1, T2, ... TN)>
? Comments?
Hmmm, that was an accidental feature... so we could construct a case where one could exploit this functionality: struct X { template<typename F> struct result {}; template<> struct result<X(int&)> { typedef int& type; } int& operator()(int&); operator bool() const; }; template<typename F> typename boost::result_of<F(int&)>::type foo(F f); bool foo(bool); X x; foo(x); // works if we use inheritance for X::result<X(int&)>, fails otherwise Now, in C++0x, the metaprogramming-based implementation and specification of result_of is going to disappear in favor of decltype. With a decltype-based implementation, the code above would be ill-formed, so I think the right answer is "do the same thing that the decltype implementation would do." From this viewpoint, the SFINAE-propagating feature of Boost's result_of is really a bug that we should fix. Cheers, Doug

Doug Gregor wrote:
Hi Tobias,
On Dec 27, 2006, at 7:51 AM, Tobias Schwinger wrote:
Hi Doug and Boost community,
I noticed that the Boost implementation of result_of uses inheritance to forward the type member of a nested result metafunction, so that result_of<...>::type can result in a substitution failure (and not an error when used in an appropriate context) if the nested result class template does not have a type member.
It's surely a nice feature (also because it usually stops trace diagnostics at a reasonable point in case of an error). Should the standard text perhaps encforce that kind of implementation - e.g:
[tr.func.ret.ret] 2 5 (b)
If N>0, result_of<F(T1, T2, ... TN)> inherits from F::result<F(T1, T2, ... TN)>
? Comments?
Hmmm, that was an accidental feature... so we could construct a case where one could exploit this functionality:
struct X { template<typename F> struct result {}; template<> struct result<X(int&)> { typedef int& type; }
int& operator()(int&);
operator bool() const; };
template<typename F> typename boost::result_of<F(int&)>::type foo(F f); bool foo(bool);
X x; foo(x); // works if we use inheritance for X::result<X(int&)>, fails otherwise
It doesn't really take that esoteric of a case to be useful, though. Let's consider a variadic function wrapper and a call that doesn't fit the arity of the wrapped object: Now, with "SFIANE propagation" we get "no match for call to <wrapper>..." reported in client code - without "SFINAE propagation" we get "no match for call to <wrapped>...", reported in the gory details of the wrapper and (possibly lots of) trace diagnostics burying it.
Now, in C++0x, the metaprogramming-based implementation and specification of result_of is going to disappear in favor of decltype.
We can remove that nested result class template, then...
With a decltype-based implementation, the code above would be ill-formed, so I think the right answer is "do the same thing that the decltype implementation would do."
... but I guess we might be able to use that very language extension to detect whether a function object has a call operator that suits given argument types. Further, a client may still define a specialization for std::result_of to control whatever is in there. Not trying to convince you - just thinking aloud... Thanks for your reply. Regards, Tobias

On Jan 8, 2007, at 1:19 PM, Tobias Schwinger wrote:
Doug Gregor wrote: [snipped] It doesn't really take that esoteric of a case to be useful, though. Let's consider a variadic function wrapper and a call that doesn't fit the arity of the wrapped object:
Now, with "SFIANE propagation" we get "no match for call to <wrapper>..." reported in client code - without "SFINAE propagation" we get "no match for call to <wrapped>...", reported in the gory details of the wrapper and (possibly lots of) trace diagnostics burying it.
Right. The esoteric case came to mind first... I think that says something about my mental state :)
Now, in C++0x, the metaprogramming-based implementation and specification of result_of is going to disappear in favor of decltype.
We can remove that nested result class template, then...
Right! But we don't get SFINAE-like behavior.
With a decltype-based implementation, the code above would be ill-formed, so I think the right answer is "do the same thing that the decltype implementation would do."
... but I guess we might be able to use that very language extension to detect whether a function object has a call operator that suits given argument types.
This has gone back-and-forth many times. At present, the expression inside decltype won't have SFINAE capabilities, just like sizeof doesn't work with SFINAE now.
Further, a client may still define a specialization for std::result_of to control whatever is in there.
That's actually undefined behavior; at least, if the client specializes std::result_of to return something other than the actual return type, she invokes undefined behavior.
Not trying to convince you - just thinking aloud...
I'd like to have SFINAE behavior if we can get it, but we really did have decltype in mind when we wrote result_of, and we shouldn't promise more than what decltype can deliver (even if we can get it with today's "result_of", which is broken in many other ways). Cheers, Doug

Doug Gregor wrote: <snip>
Now, in C++0x, the metaprogramming-based implementation and specification of result_of is going to disappear in favor of decltype. We can remove that nested result class template, then...
Right! But we don't get SFINAE-like behavior.
With a decltype-based implementation, the code above would be ill-formed, so I think the right answer is "do the same thing that the decltype implementation would do." ... but I guess we might be able to use that very language extension to detect whether a function object has a call operator that suits given argument types.
This has gone back-and-forth many times. At present, the expression inside decltype won't have SFINAE capabilities, just like sizeof doesn't work with SFINAE now.
That's not what I meant. I'll try with code: struct reserved { }; template<class FuncObj> struct tester : FuncObj { using FuncObj::operator(); reserved operator()(...) const; // [...] }; // Check whether the type of the expression 'some_tester(a0...aN)' // is 'reserved' in a traits template. I guess that would require the function object to be copy-constructible, but that doesn't seem like a show stopper to me... Regards, Tobias
participants (3)
-
Doug Gregor
-
Tobias Schwinger
-
Tobias Schwinger