3

I am learning about C++20 ranges and came across this term "customization point object". I learned and understand that it is a const semiregular function object used for solving customization dispatch from Barry's C++ blog about Niebloids and Customization Point Objects and What are customization point objects and how to use them?.

With this understanding, I thought "customization point" as "the generic code that is customizable by user". For example, function template std::swap is a "customization point" since we as user can write customized function overloads with specific types.

But today when I read C++ standard library design guidelines, I found this:

Make it clear what the customization points are.

Then I doubted my understanding about "customization point" might be wrong, since if customization point is just "generic code like templates", then why do we need to "make it clear" since the definition of template is already clear? Is the meaning of "customization point" something else?

I also found how Eric Niebler defines customization point in Eric Niebler's blog: Customization Point Design in C++11 and Beyond:

hooks used by generic code that end-users can specialize to customize the behavior for their types.

I am not a native English speaker and I am not sure I understand this definition, here is my two guesses:

  1. for function template std::swap, the customization point is not std::swap itself, it is the template parameters (which in this case, type), it is the thing that we specialize to create a customization version from the standard version.
  2. customization point is just the customization version from the standard version, like if we define swap(MyType t1, MyType t2), then this customized definition is the customization point.

Do I get the right understanding with either of these two? What is the clear definition of "customization point"? And why "customization point object", as a function object used to circumvent ADL, is called "customization point object"?

Mat
  • 202,337
  • 40
  • 393
  • 406
Waker
  • 216
  • 9
  • 1
    I believe "customization point" is a generic phrase with no clear definition, akin to "some process that you can follow to customize behavior", but I might be wrong – yeputons Jul 20 '23 at 17:31
  • 1
    "customization point object" is a bit of a misnomer at this point. The first few added to the library (`ranges::begin`, `ranges::swap`, etc.) were things with customization points, but not all of them are. For example, `std::ranges::views::single` is a customization point object, but there is absolutely nothing to customize. – Artyer Jul 20 '23 at 19:54

2 Answers2

4

A customization point (not "customization point object", that's a different thing) is something you need to write/modify/specialize to signal to generic code how to do X with your type, or signal some property of your type.

A popular kind of customization point is a template that one can specialize:

// --- In a library:
template <typename T>
struct PrintTraits
{
    static void Print(const T &value) = delete;
};
// Perhaps a few specialzations for the standard types here.

template <typename T>
void Print(const T &value)
{
    PrintTraits<T>::Print(value);
}

// --- In user code:
struct A {int x = 42;};

template <>
struct PrintTraits
{
    static void Print(const A &value) {std::cout << value.x << '\n';}
};

int main()
{
    A a;
    Print(a);
}

Here template <typename T> struct PrintTraits is a customization point.

Another popular kind of customization point is a function that's called via ADL:

// --- In a library:
namespace Lib
{
    inline void adl_Print() {} // A fallback ADL target.

    template <typename T>
    void Print(const T &value)
    {
        adl_Print(value);
    }
}

// --- In user code:
struct A
{
    int x = 42;

    // This can also be outside of the class in the same namespace.
    friend void adl_Print(const A &value) {std::cout << value.x << '\n';}
};

int main()
{
    A a;
    Lib::Print(a);
}

Now, std::swap() is not a customization point, since you can't customize its behavior (it always calls a move constructor and an assignment).

The customization point here is swap() called via ADL. You'll see a lot of code like this:

using std::swap;
swap(foo, bar);

This calls the customized swap via ADL if it's a thing, or falls back to move-then-assign std::swap if there's none.

why do we need to "make it clear" since the definition of template is already clear?

Because complex templates are hard to read. It really helps if there's a clear documentation on what can be customized and how: "if you want to customize how such-and-such algorithm swaps your type, define your own swap".


Then, a customization point object is a weird name. They are not a subset of customization points.

They are objects (global constant variables) with overloaded templated operator() (essentially lambdas?). When called, they dispatch the call to a certain customization point for the argument type. E.g. std::ranges::swap calls ADL swap if it exists, and falls back to std::swap.

It's similar to using std::swap; swap(foo, bar);, but doesn't look as ugly, and can incorporate extra checks for the customized swap to ensure that it's sane.

My examples above use void Print(...) for this purpose (a function, so I can't call it a "customization point object").

Using objects for this purpose has some benefits, such as:

  • The user can't overload them, bypassing whatever checks you want to add on top of the underlying customization point.

  • You can pass them to templates: std::invoke(Print, foo) wouldn't compile for a template function Print, but would compile if it was a customization point object (a variable).

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 2
    "*Them being "objects" isn't really important.*" Um, no. Being an object is *really important*. It's important because they're *not functions* and therefore *cannot* be overloaded or specialized. Any overloading happens in `operator()` itself and cannot be affected by any external code. – Nicol Bolas Jul 20 '23 at 18:08
  • 1
    @NicolBolas Fair enough, edited. I was going to say that you can just document that they shouldn't be specialized, but then I remembered I did have users specializing my functions despite the documentation. – HolyBlackCat Jul 20 '23 at 18:23
  • Thank you for your answer! About `std::swap`, will defining customization version of `swap` in `std` namespace work? Will that make `std::swap` a customization point? – Waker Jul 20 '23 at 19:17
  • 1
    @Waker Adding stuff to `namespace std` is undefined behavior. And, see, this is the point NicolBolas was making. You're not supposed to, that's why you make them objects to stop users from attempting this. – HolyBlackCat Jul 20 '23 at 19:24
  • And for customization definition, does it ever make sense to customize for non-user-defined types? – Waker Jul 20 '23 at 19:32
  • 1
    @Waker *"Is it necessary for the customization definition to be at a different namespace"* I don't think so. *"does it ever make sense to customize for non-user-defined types"* Without specifics, that sounds like poor design to me. It's the job of the library author to customize it for built-in types. – HolyBlackCat Jul 20 '23 at 19:35
3

And why "customization point object", as a function object used to circumvent ADL, is called "customization point object"?

I'll answer your second question first. The term is meant to be literal: it is a thing which is specifically an "object" (note: functions are not objects) which implements a "customization point".

What is the clear definition of "customization point"?

Within the context of C++ templates, the term is usually used to refer to a callable thing (function or function-like object, depending on the implementation) that one or more templates will invoke, and that particular callable thing can have its behavior customized when applied to user-provided types without modifying the template that invokes the customization point.

Consider std::sort. Sorting operations (usually) need to be able to swap objects in a sequence. That is performed (pre-C++20) by having std::sort invoke swap(a, b) (with an assist from the using std::swap; statement). The customization point in this case is swap, and it is being invoked by sort.

If your type is in a namespace and you have a swap function in that same namespace, it will use ADL to access your swap function in your type's namespace. That's how you customize this particular "customization point".

However, many types can perform swap operations via copying or moving. So any type that is copyable or moveable is conceptually swappable. Therefore, if the behavior is not customized, default behavior can be used. This default is implemented by std::swap (which also implements the swapping behavior for types that aren't in any namespace).

So a "customization point" consists of 3 things:

  1. The mechanism of invoking it in a way the user can customize.
  2. Optional default behavior if it is not customized.
  3. The mechanism of defining your customization of it.

The above ADL-based mechanism is one way to implement a customization point. C++20's customization point objects are another (better) way of defining these. The invocation mechanism is just call the functor using its full-qualified name. The default behavior is defined by the operator() of that functor. And the customization mechanism is defined by various overloads or if constexpr constructs within the functor.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982