53

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?

metalfox
  • 6,301
  • 1
  • 21
  • 43

2 Answers2

55

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.

Explanation (a summary of N4381)

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 uses std::begin idiomatically:

    using std::begin;
    begin(a);

    If the call to begin dispatches to a user-defined overload, then the constraint on std::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 object std::begin. Since lookup has found an object and not a function, the second phase of lookup is not performed. In other words, if std::begin is an object, then using std::begin; begin(a); is equivalent to std::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.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Shouldn't it be `constexpr auto begin = __detail::__begin_fn{}` in the anonimous namespace? – metalfox Nov 27 '18 at 10:17
  • 8
    Note that the ODR trickery is made moot by C++17 inline variables. Now `inline constexpr __detail::__begin_fn begin{};` should suffice. – cpplearner Nov 27 '18 at 10:18
  • Is it an implementation detail of the standard library or must the user take care of customization points? – Oliv Nov 27 '18 at 13:01
  • @Oliv If I understand it correctly, there is no need to "take care" of customization points - except providing the appropriate overload of the function with the same name. Same as it has been for e.g. `swap`. – lubgr Nov 27 '18 at 13:05
  • 1
    Re the draft by Eric Niebler. He has a great blog post about customization points here: http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ – AndyG Nov 27 '18 at 13:42
  • 3
    There are no CPOs in `std::` directly, IIRC. – T.C. Nov 28 '18 at 09:05
  • If we're going all out, you should also wrap the `begin` object in an inline namespace – Barry Dec 16 '18 at 04:52
  • Where [in the standard](http://eel.is/c++draft/customization.point.object) do you get the requirement that a CPO must be the same across TUs? Or is that a relict from their conception with the range-ts? – Deduplicator Jun 26 '19 at 16:54
  • @Deduplicator Not sure whether I understand what you mean, where in the answer is a reference to such a requirement? – lubgr Jun 27 '19 at 08:48
  • 1
    If I'm not wrong, customization points like `std::begin` are still free functions and not function objects as for C++20 isn't? The only customization points implemented as functions objects are those from the ranges library like `std::ranges::begin`. – ABu Jul 23 '19 at 17:00
  • 2
    @Peregring-lk I think so too, otherwise this would break backward compatibility. – lubgr Jul 23 '19 at 17:25
  • @lubgr Thanks. It was in doubt because `std::begin` is the most usual example used to explained customization point objects while not being actually implemented that way. – ABu Jul 23 '19 at 21:19
  • Confusing. I got to this page because I was thinking about the growing trend in the C++ standard to work with customization points that are defined by classes in general, not just those that satisfy the "Callable" concept. E.g. the Coroutines TS relies heavily on several classes to be defined. But here it was stated that CPOs are "Function Objects." I hope that's not correct because if so it would be yet another terrible nomenclature that is confusing. It seems to me that they are all CPOs, regardless of whether they have operator(). Otherwise, what do you call non-callable ones? – Daniel Russell Mar 04 '20 at 21:13
22

"Customization point object" is a bit of a misnomer. Many - probably a majority - aren't actually customization points.

Things like ranges::begin, ranges::end, and ranges::swap are "true" CPOs. Calling one of those causes some complex metaprogramming to take place to figure out if there is a valid customized begin or end or swap to call, or if the default implementation should be used, or if the call should instead be ill-formed (in a SFINAE-friendly manner). Because a number of library concepts are defined in terms of CPO calls being valid (like Range and Swappable), correctly constrained generic code must use such CPOs. Of course, if you know the concrete type and another way to get an iterator out of it, feel free.

Things like ranges::cbegin are CPOs without the "CP" part. They always do the default thing, so it's not much of a customization point. Similarly, range adaptor objects are CPOs but there's nothing customizable about them. Classifying them as CPOs is more of a matter of consistency (for cbegin) or specification convenience (adaptors).

Finally, things like ranges::all_of are quasi-CPOs or niebloids. They are specified as function templates with special magical ADL-blocking properties and weasel wording to allow them to be implemented as function objects instead. This is primarily to prevent ADL picking up the unconstrained overload in namespace std when a constrained algorithm in std::ranges is called unqualified. Because the std::ranges algorithm accepts iterator-sentinel pairs, it's usually less specialized than its std counterpart and loses overload resolution as a result.

T.C.
  • 133,968
  • 17
  • 288
  • 421