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:
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.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"?
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).
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:
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.
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