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
f
of typeF
is Callable for argument typesArgTypes
and return typeR
if 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 defined template < 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:
namespace
std {
[...]
// 20.10.4.3, type properties:
[...]
template
<
class
T>
struct
is_nothrow_destructible;
template
<
class
T>
struct
has_virtual_destructor;
template
<
class
,
class
R =
void
>
struct
is_callable;
// not defined
template
<
class
Fn,
class
... ArgTypes,
class
R>
struct
is_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
<
class
Fn,
class
... ArgTypes,
class
R>
struct
is_callable<Fn(ArgTypes...), R>;
Condition:
The expression
INVOKE(declval<Fn>(), declval<ArgTypes>()..., R)
is well formed when treated as an unevaluated operand.
Preconditions:
Fn
and all types in the parameter packArgTypes
shall 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