std::result_of and SFINAE
ISO/IEC JTC1 SC22 WG21 N3462=12-0152 - 2012-10-18
Eric Niebler (eric.niebler@gmail.com)
Daniel Walker (daniel.j.walker@gmail.com)
Joel de Guzman (djowel@gmail.com)
This paper deals with the use of result_of in contexts where SFINAE is a consideration; i.e., in function signatures. It is an update to N3436. result_of is intended to be a simple way to compute the result type of some callable when provided with arguments of specified types. It is a shorthand for a longer type computation using decltype and declval. However, there are cases where the use of result_of leads to a hard error whereas the equivalent use of decltype and declval would not. This happens in function signatures, when the direct use of decltype of an ill-formed call expression causes the function to be dropped due to SFINAE.
This paper addresses the issue by making result_of SFINAE-friendly. It does this by conditioning the presence of the nested type typedef on whether the call expression is ill-formed or not.
This paper will do the following:
- Demonstrate the issue.
- Present complaints from users demonstrating that the current behavior is unsatisfactory.
- Suggest core wording changes that will address the problem.
- Weigh the pros and cons of accepting this resolution vs. others.
Problem
Trivial Example
Reports from the Field
Solution
Proposed Resolution
Options Considered and Dismissed
Implementation Experience
Differences from the Pre-Portland Mailing
Acknowledgements
Appendix A: Example of an Error in a Non-Immediate Context
Appendix B: Example of Why Fn Shouldn't Be Required to Be a Callable Type
Problem
The direct use of decltype in a function signature would cause SFINAE to kick in when the expression in the decltype is invalid. In contrast, the use of result_of in a function signature, as it is currently specified, causes a hard error because the call expression appears in a non-SFINAE context: within the definition of the nested type typedef.
Trivial Example
Below is a program that compiles when the return type is declared using decltype, but that fails to compile when usingresult_of.
//#define BUGBUG #include <type_traits> #include <utility> #include <string> struct eat { template<typename T> eat(T const &) {} }; struct not_incrementable {}; struct inc { template<typename T> auto operator()(T t) const -> decltype(t++) { return t++; } }; template<typename A> #ifdef BUGBUG typename std::result_of<inc(A)>::type // HARD ERROR HERE #else decltype(std::declval<inc>()(std::declval<A>())) // SFINAE HERE #endif try_inc(A a) { return inc()(a); } not_incrementable try_inc(eat) { return not_incrementable(); } int main() { int x = try_inc(1); // OK not_incrementable y = try_inc(std::string("foo")); // OK, not_incrementable }
Reports from the Field
You might wonder if this is really a problem that needs to be fixed, or whether instead it is simply a case of people misusing result_of. Even if result_of were never intended to be used this way, there is evidence that users find the current behavior unsatisfactory:
-
Boost
-
When
boost::result_ofwas switched to usedecltype, some of Boost's tests started failing. After investigation, we discovered that the TR1-styleresult_ofhad been implemented such that it was SFINAE-friendly, and that folks had discovered this and come to rely on it. The amount of affected code within Boost was small, but Boost became concerned about its downstream users and are now in the process of making theirdecltype-basedresult_ofSFINAE-friendly to prevent downstream breakage. (Aside: the problems in Boost have already been found by users and reported both on StackOverflow and in Boost's bug tracker.)
GCC bug report
-
The linked bug report, entitled "substitution failure reports error with result_of", describes this problem. Says Paolo Carlini:
"Unfortunately, I don't think you can use std::result_of for sfinae purposes, if I understand correctly it's a well know annoyance which you have to overcome by open coding with std::declval."
LWG DR1225
-
The submitter of DR1225 wants
std::result_ofto be SFINAE-friendly (among other things) and offers wording. He is under the impression that this change would require compiler support (perhaps in order to correctly handle errors in the non-immediate instantiation context; see CWG DR1227). This part of the DR was rejected, the rationale given being: "The wish to changeresult_ofinto a compiler-support trait was beyond the actual intention of the submitter Sebastian." Note that a SFINAE-friendlyresult_ofcan be implemented in standard C++11, at least insofar as it be allowed to fail when the first error appears in a non-immediate context (see Appendix A).
Complaints from Boost's Users
-
From: Ian McCulloch
Subject: Re: result_of tutorial
Date: 2005-03-17 08:22:29 GMT
Thanks Peter, I think I understand how it works now. Unfortunately, the lack of SFINAE is a real showstopper for me. I want to write functions like
template <typename T>
typename result_of<negate(T)>::type
operator-(T const& x)
{
return negate()(x);
}
but the lack of SFINAE here makes boost::result_of essentially useless for this. But I imagine it won't be difficult to make a new version based on boost that would work."
A Blog Post
-
Although written in Japanese, the author shows an
invokefunction defined withresult_ofthat fails to compile because it (result_of) is not SFINAE-friendly. It also calls outstd::common_typeas another trait that would benefit from being SFINAE-friendly.
Solution
Notes About the Proposed Wording Change
The intention of the proposed change is to condition the presence of the nested type typedef of result_of on whether the call expression used in the decltype-specifier is well-formed. The changes occur in Table 57 (section 20.9.7.6, [meta.trans.other]). An attempt is made at the standardese to permit result_of to issue a hard error if and only if the first error is encountered in a non-immediate context. This bit of language borrows heavily from the specification ofis_assignable in Table 49 (section 20.9.4.3, [meta.unary.prop]). (See Appendix A for an example of an error in a non-immediate context.)
Proposed Resolution
-
Change Table 57 "Other transformations" [meta.trans.other] as indicated:
template <bool B, class T = void>
struct enable_if;If Bistrue, the member typedeftype
shall equalT; otherwise, there shall be
no membertypedeftype....
template <class Fn,
class... ArgTypes> struct
result_of<Fn(ArgTypes...)>;Fnshall be a callable
type (20.8.1), reference to
function, or reference to
callable type. The
expression
decltype(INVOKE(declval<Fn>(),shall
declval<ArgTypes>()...))
be well formed.
Fnand all types in the parameter
packArgTypesshall be complete
types, (possibly cv-qualified)void,
or arrays of unknown bound.If the expression
INVOKE(declval<Fn>(),
declval<ArgTypes>()...)
is well formed when treated as an unevaluated operand (Clause 5), tThe member typedeftypeshall name the type
decltype(INVOKE(declval<Fn>(),; otherwise, there shall be no
declval<ArgTypes>()...))
membertype. Access
checking is performed as if in a
context unrelated toFnand
ArgTypes. Only the validity of the
immediate context of the
expression is considered. [ Note:
The compilation of the expression
can result in side effects such as
the instantiation of class template
specializations and function
template specializations, the
generation of implicitly-defined
functions, and so on. Such side
effects are not in the "immediate
context" and can result in the
program being ill-formed.
-end note ]
Options Considered and Dismissed
Eric Niebler started a discussion on the -lib reflector about result_of and SFINAE in c++std-lib-32993. Below are some of the considerations brought up in the ensuing thread, and the authors' thoughts.
Should result_of simply be deprecated?
In c++std-lib-32999, Nikolay Ivchenkov floats the idea that result_of be deprecated, thereby solving the SFINAE problem by encouraging users to use decltype and declval directly. Others (namely Howard Hinnant and Pete Becker) echoed Nikolay's sentiment that result_of's unconventional use of function types is confusing to some users. In c++std-lib-33010, Anthony Williams points out that result_of<X(Y)>::type is simpler than the equivalent type calculation involving decltype anddeclval. The authors of this paper agree and do not recommend deprecating result_of.
Should result_of be implemented in terms of more general and idiomatic traits?
In c++std-lib-33011, Howard Hinnant described an interface that he has come to prefer: std::invoke_of and std::invokable. Rather than taking one template argument of function type, invoke_of would take N parameters, the first of which is the callable type and the rest are the types of the arguments. invokable is a Boolean trait that takes the same parameters and computes whether the specified call expression is well-formed or not. result_of is trivially implemented in terms of such traits.
The authors of this paper are not opposed to this change. It is, however, a larger change than necessary. As such, the authors are not proposing it here. Note that adopting the resolution suggested in this paper would not block any future proposal that recommends such a change.
Shouldn't we also address the other traits that could be made SFINAE-friendly?
As noted by some users and by Marc Glisse in c++std-lib-32994, result_of is far from the only trait that could benefit from the SFINAE treatment. iterator_traits and common_type are obvious candidates. The authors of this paper have chosen to narrowly focus on result_of for lack of time.
Implementation Experience
Has this been implemented yet? Yes. In c++std-lib-33002, Howard Hinnant claims that his libc++ library has already shipped a SFINAE-friendly result_of. In c++std-lib-33013, he links to his implementation. It can be found athttp://llvm.org/svn/llvm-project/libcxx/trunk/include/type_traits.
Daniel Krügler claimed in a private exchange to have recently implemented a SFINAE-friendly result_of for gcc.
Also, Boost recently switched to a SFINAE-friendly result_of, although it hasn't shipped it yet at the time of writing (2012-09-20).
Differences from Pre-Portland Mailing
Since the pre-Portland mailing went out, Daniel Krügler brought two issues to the authors' attention. Both have been addressed by changing the proposed wording. The changes are as follows:
- The requirement on
Fnto be a "callable type (20.8.1), reference to function, or reference to callable type" has been struck. - An additional requirement for type completeness of
Fnand all the types in the parameter packArgTypeshas been added.
In support of (1), Daniel Krügler observes that simply removing that requirement allows for SFINAE to drop overloads based on the non-callablility of the Fn template parameter. "Not ever callable" is not that logically different from "not callable with certain arguments," and would in fact be handled naturally by the most naive implementation. Appendix Bshows an example where this distinction makes a difference.
The change described in bullet (2) is to make all the types in the template parameter complete. Daniel Krügler describes his reasoning as follows in a private email:
The other point (I mentioned it already in my other follow-up) is that your suggested wording omits an important constraint, namely type completeness. The reason for this can be directly deduced from the definition of INVOKE in 20.8.2 p1: Bullets 1 and 3 require to determine whether T1 is a of the same or of a derived type of the class type of the pointer of member. Also, we need a complete type of Fn, if this is a class type with a potentially existing function call operator. As a conservative start I suggest to impose the very same precondition as we have for is_constructible or for common_type [...]
Acknowledgements
Many thanks to those on the committee reflector who provided feedback about this issue; namely, Marc Glisse, Nikolay Ivchenkov, David Abrahams, Anthony Williams, Pete Becker, Peter Dimov, Doug Gregor and especially Daniel Krügler, who suggested wording and pointed out a number of ways to improve the resolution. Thanks are also due to the Boost users and developers who pushed for this change and made suggestions, especially Michel Morin and Jeffrey Lee Hellrung, Jr.
Appendix A: Example of an Error in a Non-Immediate Context
The current proposal allows that while testing an expression for well-formed-ness, a hard error can occur. No attempt is made to make result_of usable with such "indirectly" ill-formed expressions. Below is an example of a callable expression that will trigger a hard error, regardless of whether result_of is "SFINAE-friendly" or not.
template<class X> struct Fail { static_assert(sizeof(X)==0,"duh"); typedef int type; }; struct Fun { template<class T> typename Fail<T>::type operator()(T) { return 0; } }; template<class T> typename std::result_of<Fun(T)>::type foo(T) { return 0; } template<class T> int foo(...) { return 0; } int main() { foo(0); }
Appendix B: Example of Why Fn Shouldn't Be Required to Be a Callable Type
This example comes from Daniel Krügler. It demonstrates some legitimate code where you'd like SFINAE to exclude the use of result_of when the callable is not, in fact, callable. If the original requirement on the callability of Fn is retained, the code below becomes undefined, and possibly a hard error.
#include <type_traits> template<class Fn> typename std::result_of<Fn(int)>::type f(Fn& t); template<class T, class U> int f(U u); struct S {}; int main() { f<S>(0); }
本文探讨了std::result_of在SFINAE上下文中的使用问题,并提出了一种解决方案,使std::result_of在某些情况下能更好地遵循SFINAE原则。
2291

被折叠的 条评论
为什么被折叠?



