1. Revision History
1.1. Revision 4 - November 1st, 2018
-
Change primary author and point of contact.
-
Prepare paper for LWG with wording for parts approved by LEWG.
-
Separated out the idea of a
selects the first overload that matches arguments exactly (not this paper)std :: exact_overload -
Select implementation to move forward with: forwarding calls for not-quite-derivable types while accepting that overload resolution might not work exactly as planned due to the greediness of perfectly forwarding types. Other overload strategies can be used for other types.
1.2. Revision 3
-
Signal a limitation on the design: final structs, reference_wrapper/perfect forwarders, and other not-quite-derivable types.
-
Remove the wording waiting for a decision on how deal with these limitations.
1.3. Revision 2
-
Add
and conditionalconstexpr
.noexcept -
Confirmed the use of universal references as parameters of
.std :: overload -
Ensure that cv-qualifiers and reference-qualifiers are forwarded correctly.
-
Note that the use case for
-marked Callables is acceptable.final -
Check the working with an expert from LWG before sending a new revision to LEWG and LWG.
1.4. Revision 1
This paper has been splintered into 3 or more proposals, following discussion in the Kona meeting for the original [p0051r0]:
-
selects the best overload using C++ overload resolution (this paper).std :: overload -
Separate out
selects the first overload that matches using C++ overload resolution (not this paper)std :: first_overload -
Separate out providing access to the stored function objects when they are stateful (not this paper).
1.5. Revision 0
-
Initial release.
2. Motivation
This paper proposes a
function for the C++ Standard Library. It creates an unspecified object which uses C++ overload resolution to select one of the provided functions to call. The overloaded functions are copied and there is no way to access to the stored functions: that is another proposal.
Lambda expressions, library-defined functions objects, and other callables/
ables are unable to be overloaded in the usual way, but they can be 'explicitly overloaded' using the proposed overload function. It is primarily useful for creating visitors, e.g. for
. See below:
Shared Code | |
---|---|
| |
Currently | With Proposal |
|
|
3. Design
is designed to work with anything that is
-able, à la
or equivalent. That means it works with functions, class types with
, pointer to members (member function pointers and member object pointers), and
types. This allows the full gamut of C++'s expressivity to be utilized in an overload expression.
The interface is a simple variadic function which returns an instance of an unspecified type. It is unspecified because its implementation techniques and internals are not to be relied upon by the user. As such, there is no way to retrieve a function out of the result of a
call. This is intentional: if the user wants to keep a reference to the invokable, they will have to pass it in via
or similar wrapper. All functions are taken by forwarding reference and behave as if stored by
, with the caveat that they must behave as-presented.
The resulting call operation will be marked
if the selected overload is
and
evaluates to true.
is also propagated properly.
3.1. Perfect Argument Match vs. Forwarding?
This touches at the heart of some implementation problems. Types which introduce this problem include anything the implementation cannot derive from directly, as well as types which wrap other types and introduce forwarding argument calls:
-
member function pointers
-
member object pointers
-
function pointers
-
instances of types marked final
There are 2 known ways to implement this in the face of such: one is to perfectly mimic the function call’s arguments in the case where it cannot be easily inherited into the
of
's unspecified return type. This is usually via template specialization or similar technique that pulls out the type of every single argument (plus any object type in the case of pointer-to-members). This gives us exactly perfect overload resolution that copes with things as expected by the developer. For example:
with the first implementation strategy will always properly select the right call with
and
. It comes with a cost, however: more moves and copies are performed than necessary. This is due to leaking some of the implementation details because the compiler still has to go through the "shim" layer to handle not-quite-derivable types, before hitting the real function call. Still, when used as the implementation technique for an overload set, a function that has very-close-conversions will not be ambiguous.
This was seen as a problem in previous revisions and by people who read the paper, and it was agreed that the calls should forward arguments whenever possible.
This led to the second, preferred implementation. By using a perfectly forwarding wrapper,
achieves the zero-argument-passing overhead that was desirable. We performed
with
to SFINAE the call away when it is not appropriate. The problem became that with this strategy, the use of
and
are ambiguous if and only if one uses non-derivable types or types with forwarding calls (e.g.,
). This presented a conundrum for the previous revisions of the paper. Ambiguity due to overload resolution based on cv-qualifiers of contained type’s
also happened. A regular lambda that takes a
and a
-marked lambda that takes a
result is an ambiguous overload set for
's implementation due to the differences between
and non
.
This revision decides that it is better to make a choice and let other proposals provide more fine-grained methods of overloading. In particular, this revision chooses efficiency over overload resolution issues: by using the perfectly-forwarding,
-SFINAE’d calls to handle wrapping non-derivable types, we can maintain a minimal amount of moves or copies. This is highly desirable. Additionally, the other problems can be potentially worked around by properly selecting which functions go into
or writing additional lambdas, while forwarding efficiency cannot be worked around adequately by a user of
. This makes the use of perfectly forwarding calls much more important for non-derivable types.
3.2. INVOKE
ables
The handling of all of these will be with
. The publicly available implementation does a few things extra, such as allowing for any wrapper type (not just
) to be used with pointer-to-members and some additional syntax choices, but for the sake of standardization the only requirement we are placing on implementers is that anything placed into
must be
able.
3.3. Shipping
This paper has been in limbo since pre-2015, based on not getting perfectly-right implementation. There are some corners of this that do not behave exactly as expected due to forwarding call wrappers, as discussed above. However, working with
is made much simpler by this: it would be better to ship this or a version of this for C++20 so that people do not continue to recreate the same thing in their codebases, but with suboptimal design choices.
It is a shame that
did not ship with this feature and we do need something to fill in this gap.
4. Future
4.1. Library
When creating this paper, it was clear that other forms of overload matching might be ideal to have. For example, a
was discussed that would not necessarily pick the best overload not by C++ overload resolution rules, but pick the first function that was properly callable with the parameters. This means that even if one function "matches" better by C++ overload resolution rules, if a function prior to it is callable but has a worse ranking due to e.g. conversions, the function that was earlier in the list that had the conversions would be called rather than the best match. This would allow users to willingly trade between different types where conversions and other things make it ambiguous. This would also allow an explicit ranking.
This is not proposed in this paper, albeit it is strongly encouraged for others to contribute such ideas in another paper. In particular, the authors here think that a
/
would be highly desirable for users who have complex variants and other corner-case ambiguities and are willing to settle for strict, perfect matching of arguments. Again, this is not proposed in this paper.
4.2. Language
Given the various problems with defining overload sets, it seems like this feature might be better fit as a language feature. Thankfully, a language solution to this problem can be developed in-parallel or even after this paper and does not need to impede the progress of this paper. A language feature addressing this problem would be to create an overloaded set out of a number of
ables, and not have to suffer any of the usual problems with having to choose between different implementation techniques. This could even be an extension to [P1170R0].
5. Proposed Wording
Note: The following changes are relative to the post-Rapperswil 2018 working draft of ISO/IEC 14882, ([N4762]).
Note: The � character is used to denote a placeholder number which shall be selected by the editor.
5.1. Proposed Feature Testing Macro
The proposed feature testing macro is
.
5.2. Intent
The intent of this wording is to produce an unspecified object that:
-
is
and hasconstexpr
call operators,constexpr -
is
to construct if all the passed in types are nothrow move constructible,noexcept -
is
to call if the selected overload isnoexcept
,noexcept -
implements a (potentially overloaded) call operator usable in the form,
,obj ( args ...) -
works with any
/INVOKE
-able callable,std :: invoke -
and, forwards all arguments to its underlying function call, if the implementation needs to.
The implementation may not need to explicitly forward the arguments,but the specification will be written as if all arguments are perfectly forwarded from the the call on an
-returned object and given to the proper underlying call.
5.3. Proposed Wording
Append to §16.3.1 General [support.limits.general]'s Table 35 one additional entry:
Macro name Value __cpp_lib_overload 201811L
Insert a new entry into §19.1 [utilities.general]'s Table 38:
Subclause Header(s) 19.� Overload function objects <overload>
Insert a new sub-section §19.� Overload function objects [overload.obj] in §19 [utilities] as follows:
19.� Overload function objects [overload.obj]19.�.1 Header
synopsis [overload.obj.syn]
< overload >
// 19.�, Overload template constexpr /* see below */ overload ( Args && ... args ) noexcept ( /*see below*/ ) 19.�.2
[overload.obj.overload]
overload template < class ... Args > ; constexpr /* see below */ overload ( Args && ... args ) noexcept ( /*see below*/ ); Returns: An instance of an unspecified type of function object that behaves as if all the passed-in callables were overloaded (11.3 [over.match]) when calling it. The overloads shall preserve,
constexpr , cv-qualifiers and reference qualifiers.
noexcept The effect of calling an instance of this type with parameters will select the best overload. Given arguments
,
a1 , ...
a2 with types
aN ,
T1 , ...,
T2 for any number
TN >= 0, a call on the resulting object will behave as if by
N . If there is no such a best overload, either because there is no candidate or that there are ambiguous candidates, the invocation expression will be ill-formed.
INVOKE ( DECAY_COPY ( arg ), forward < T1 > ( a1 ) ..., forward < TN > ( aN ) Throws: Any exception thrown during the construction of the resulting function object. If all of the constructors satisfy, then the function is
is_nothrow_constructible .
noexcept Remarks: This function as well as the overloadedfor each of
operator () on the resulting type shall be a
Args functions. The overloaded
constexpr for each
operator () in
arg on the resulting type shall be
args ... or equivalent.
noexcept ( is_nothrow_invocable_v < Arg , T1 , T2 , ..., TN > )
6. Acknowledgements
Thanks to Daniel Krügler who helped me improve the wording and pointed out to me the use case for a final Callable. Thanks to Scott Pager who suggested to add overloads for non-member and member functions (which we eventually migrated over to simply use all of
).
Thanks to Paul Fultz II and Bjørn Ali, authors of the Fit library and the FTL library, who yielded the ideas of
and helped in separating the papers out from this one.
Thanks to Matt Calabrese for his useful improvement suggestions on the library usability. Thanks to Tony Van Eerd for championing the original proposal at Kona and for insightful comments.
Thanks to Stephan T. Lavavej for pointing to CWG-1581 - "When are constexpr member functions defined?". Thanks to Peter Remmers that reported issue 16.
Thanks to Tomasz Kaminski helping me to refine the implementation for final function object and to the private discussion about the possibility to have the combination of unique_overload and first_overload as a much safer solution.
Special thanks and recognition goes to Technical Center of Nokia: Lannion for supporting in part the production of this proposal.
Special thanks to Bryce Adelstein Lelbach for his notifying the new primary author of this proposal so it could be cleaned up for C++20.