The last draft of the c++ standard introduces the so-called "customization point objects" ([customization.point.object]), which are widely used by the ranges library.
I seem to understand that they provide a way to write custom version of begin
, swap
, data
, and the like, which are found by the standard library by ADL. Is that correct?
How is this different from previous practice where a user defines an overload for e.g. begin
for her type in her own namespace? In particular, why are they objects?
A customization point object is a function object ([function. objects]) with a literal class type that interacts with program-defined types while enforcing semantic requirements on that interaction. The type of a customization point object, ignoring cv-qualifiers, shall model semiregular ([concepts. object]).
An object of type Point is created just like any other variable, except that the class constructor is automatically called when the variable is created. This is how a method is called for an object.
What are customization point objects?
They are function object instances in namespace std
that fulfill two objectives: first unconditionally trigger (conceptified) type requirements on the argument(s), then dispatch to the correct function in namespace std
or via ADL.
In particular, why are they objects?
That's necessary to circumvent a second lookup phase that would directly bring in the user provided function via ADL (this should be postponed by design). See below for more details.
... and how to use them?
When developing an application: you mainly don't. This is a standard library feature, it will add concept checking to future customization points, hopefully resulting e.g. in clear error messages when you mess up template instantiations. However, with a qualified call to such a customization point, you can directly use it. Here's an example with an imaginary std::customization_point
object that adheres to the design:
namespace a { struct A {}; // Knows what to do with the argument, but doesn't check type requirements: void customization_point(const A&); } // Does concept checking, then calls a::customization_point via ADL: std::customization_point(a::A{});
This is currently not possible with e.g. std::swap
, std::begin
and the like.
Let me try to digest the proposal behind this section in the standard. There are two issues with "classical" customization points used by the standard library.
They are easy to get wrong. As an example, swapping objects in generic code is supposed to look like this
template<class T> void f(T& t1, T& t2) { using std::swap; swap(t1, t2); }
but making a qualified call to std::swap(t1, t2)
instead is too simple - the user-provided swap
would never be called (see N4381, Motivation and Scope)
More severely, there is no way to centralize (conceptified) constraints on types passed to such user provided functions (this is also why this topic gained importance with C++20). Again from N4381:
Suppose that a future version of
std::begin
requires that its argument model a Range concept. Adding such a constraint would have no effect on code that usesstd::begin
idiomatically:using std::begin;
begin(a);
If the call to begin dispatches to a user-defined overload, then the constraint onstd::begin
has been bypassed.
The solution that is described in the proposal mitigates both issues by an approach like the following, imaginary implementation of std::begin
.
namespace std { namespace __detail { /* Classical definitions of function templates "begin" for raw arrays and ranges... */ struct __begin_fn { /* Call operator template that performs concept checking and * invokes begin(arg). This is the heart of the technique. * Everyting from above is already in the __detail scope, but * ADL is triggered, too. */ }; } /* Thanks to @cpplearner for pointing out that the global function object will be an inline variable: */ inline constexpr __detail::__begin_fn begin{}; }
First, a qualified call to e.g. std::begin(someObject)
always detours via std::__detail::__begin_fn
, which is desired. For what happens with an unqualified call, I again refer to the original paper:
In the case that begin is called unqualified after bringing
std::begin
into scope, the situation is different. In the first phase of lookup, the name begin will resolve to the global objectstd::begin
. Since lookup has found an object and not a function, the second phase of lookup is not performed. In other words, ifstd::begin
is an object, thenusing std::begin; begin(a);
is equivalent tostd::begin(a);
which, as we’ve already seen, does argument-dependent lookup on the users’ behalf.
This way, concept checking can be performed within the function object in the std
namespace, before the ADL call to a user provided function is performed. There is no way to circumvent this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With