Document number: N4446
Date: 2015-04-09
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Agustín Bergé agustinberge@gmail.com
The missing INVOKE related trait
1. Introduction
This paper proposes to introduce a new trait to determine whether an
INVOKE expression is well formed.
2. Motivation
Starting with C++11, the library introduced the pseudo-macro INVOKE as a
way to uniformly handle function objects and member pointers as call
expressions. The trait result_of was made to follow INVOKE semantics
as well. This left users —who want to follow the precedence set forth by
the standard library— with the correct result type but no direct way of
obtaining such result, and invoke implementations proliferated.
This was recently rectified by the introduction of invoke to the working
draft [N4169]. However, there is still one piece of the puzzle missing, and is
the ability to query whether an INVOKE expression is well formed when
treated as an unevaluated operand. Such functionality is currently present in
the form of C++14 SFINAE-friendly result_of, albeit in a non user-friendly
way, and it should be made readily available in trait form for the same
reasons invoke was introduced into the library.
The following is an artist depiction of such trait:
template <class T, class R = void, class = void>struct is_callable : false_type{};template <class T>struct is_callable<T, void, void_t<result_of_t<T>>> : true_type{};template <class T, class R>struct is_callable<T, R, void_t<result_of_t<T>>> : is_convertible<result_of_t<T>, R>{}; |
This trait is implemented in the wild under different names, and the check for a compatible result type is not always present. This post [call-me-maybe] shows how the implementation of such trait has been both improved and simplified by every new standard.
3. Design questions
3.1 Naming
The property this trait determines is usually referred to as whether something
is callable with given arguments [citation needed], a name dating from
before INVOKE was introduced. But perhaps callable is not the most
appropriate name, as the standard already defines a number of things by that
name:
20.9.1 [func.def]/3 A callable type is a function object type or a pointer to member.
20.9.1 [func.def]/4 A callable object is an object of a callable type.
These definitions of callable do not represent what the trait would do. Knowing whether some object is a function object or pointer to member is meaningless, as it does not tell whether operating on such an object would be well formed.
The following definition of —proper cased— Callable, introduced
and used only by std::function, matches exactly what the trait would do:
20.9.12.2 [func.wrap.func]/2 A callable object
fof typeFis Callable for argument typesArgTypesand return typeRif the expressionINVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand, is well formed.
The rest of the standard library simply requires INVOKE expressions being
well formed.
The ranges proposal [N4128] uses the term invokable instead, for things
that work with INVOKE, and defines an Invokable concept.
3.2 Compatible return types
INVOKE comes in two flavors, the primary INVOKE(f, t1, t2, ..., tN)
and INVOKE(f, t1, t2, ..., tN, R) defined as INVOKE(f, t1, t2, ..., tN)
implicitly converted to R. Both flavors can be supported with a defaulted
template argument:
template <class, class R = void> struct is_callable; // not definedtemplate <class Fn, class... ArgTypes, class R> struct is_callable<Fn(ArgTypes...), R>; |
[Note: This assumes that the resolution for LWG2420 makes
INVOKE(f, t1, t2, ..., tN, void) discard the return type. -end note]
However, if only one of those flavors would be supported there would be no missing functionality, only more work for the user.
4. Proposed Wording
This wording is relative to [N4296].
Change 20.10.2 [meta.type.synop], header <type_traits> synopsis, as
indicated:
namespacestd {[...]// 20.10.4.3, type properties:[...]template<classT>structis_nothrow_destructible;template<classT>structhas_virtual_destructor;template<class,classR =void>structis_callable;// not definedtemplate<classFn,class... ArgTypes,classR>structis_callable<Fn(ArgTypes...), R>;[...]}
Change 20.10.4.3 [meta.unary.prop], Table 49 — Type property predicates, add a new row with the following contents:
Template:
template<classFn,class... ArgTypes,classR>structis_callable<Fn(ArgTypes...), R>;
Condition:
The expression
INVOKE(declval<Fn>(), declval<ArgTypes>()..., R)is well formed when treated as an unevaluated operand.
Preconditions:
Fnand all types in the parameter packArgTypesshall be complete types, (possibly cv-qualified)void, or arrays of unknown bound.
5. References
-
[N4296] ISO/IEC JTC1 SC22 WG21, Programming Languages - C++, working draft, November 2014 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
-
[call-me-maybe] True Story: Call Me Maybe - Tales of C++ http://talesofcpp.fusionfenix.com/post-11/true-story-call-me-maybe
-
[N4169] A proposal to add invoke function template (Revision 1) - Tomasz Kaminski http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4169.html
-
[N4128] Ranges for the Standard Library, Revision 1 - Eric Niebler, Sean Parent, Andrew Sutton http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4128.html