Mircea Baja - 11 June 2025 # Niebloid, tag invoke, CPO etc. ```cpp namespace __as_awaitable { struct as_awaitable_t { template
auto operator()(_Tp&& __t, _Promise& __promise) const return tag_invoke(*this, static_cast<_Tp&&>(__t), __promise); } }; } using __as_awaitable::as_awaitable_t; inline constexpr as_awaitable_t as_awaitable{}; ``` --- # Motivation Eric Niebler drove development of new customization points techniques, first in the work for `std::ranges`, then in the work for `std::execution`. It's useful to be aware of these techniques to be able to read (library) code to understand how things actually work. --- # Motivation ```cpp namespace __as_awaitable { struct as_awaitable_t { template
auto operator()(_Tp&& __t, _Promise& __promise) const return tag_invoke(*this, static_cast<_Tp&&>(__t), __promise); } }; } using __as_awaitable::as_awaitable_t; inline constexpr as_awaitable_t as_awaitable{}; ``` Q: How do we make sense of this code? A: It obviously makes use of `tag_invoke` as a niebloid style customization point. Q: What? --- # Customization points C++ allows for all sort of customization points: - virtual function interfaces - special functions/operators e.g. constructor, `operator==` - curiously recurring template pattern (CRTP) - function overloads - [class template specialization](http://www.gotw.ca/publications/mill17.htm) - etc. They all have advantages and disadvantages. E.g. virtual function interfaces give clear compile time errors (if overload does not match interface OR missing overload), but have runtime overhead (virtual table pointer, indirect calls and allocations to return interfaces) and struggle in a generic environment (roughly because they are too precise about the types in the interface) --- # Swap - pre C++11 std ```cpp namespace std { template
void swap(T& a, T& b) { // pre C++11 code T tmp = a; a = b; b = tmp; } } ``` Pre C++11 `swap` was doing 2 copies. One would customize it e.g. `std::vector` customized it to avoid the copy of the values in the dynamic array. --- # Swap - pre C++11 customize ```cpp namespace my { class SomeType { // some private data here }; void swap(SomeType& a, SomeType& b) { // custom implementation here } } ``` Customization is also called `swap` A common pattern is for the customized `swap` to call a member `void swap(SomeType& other);` that has access to the private data. --- # Swap usage ```cpp template
void some_algoritm() { // some code, assume a and b of type T using namespace std; swap(a, b); // more code } ``` If the arguments `a` and `b` are of the type `my::SomeType`, then `my::swap` will be looked up in the namespace `my` and the customization is used, else (if no customization) `std::swap` will be used. Technique relies on argument dependent lookup (ADL) rules. But it's easy to forget to use the two steps correctly when using `std::swap`. --- # Swap - post C++11 ```cpp namespace std { template
void swap(T& a, T& b) { // post C++11 code T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } } ``` - move is used - this is example code e.g. ignoring `noexcept` specification NOTE: The customization point now is really the move constructor and assignment --- # Niebloid A **niebloid** is a customization point technique invented by Eric Niebler to improve upon the previous state of customization points in the C++ `std` library such as the one used for `std::swap`, towads one more suitable for the `std::ranges` library he was working on. It involves a global function object and fixes some issues around ADL and concept enforcement. --- # Niebloid example Say you write an `zoo` library and provide this customizable `zoo::make_sound` function. You want this to only apply to animals, with a default implementation for birds. --- # Step by step --- # Step by step (1) ```cpp namespace zoo { struct make_sound_t { auto operator()(/*function parameters here*/) const { // implementation here } }; } // namespace zoo ``` Say you have a struct `zoo::make_sound_t` that implements `operator()`. That's a functor. --- # Step by step (2) ```cpp void foo() { zoo::make_sound_t make_sound; // more code here // eventually invoke make_sound(...arguments here...) }; ``` And if we have a variable of that type, `make_sound` is a function object. --- # Step by step (3) ```cpp zoo::make_sound_t make_sound; void foo() { // more code here // eventually invoke make_sound(...arguments here...) }; ``` And if we declare it outside the scope of a function, `make_sound` is now a global function object. --- # Step by step (4) ```cpp namespace zoo { struct make_sound_t { auto operator()(/*function parameters here*/) const { // implementation here } }; inline constexpr make_sound_t make_sound; } // namespace zoo ``` And if we declare it in the header we need to ensure it's `inline` (and `constexpr`). `inline` does not mean `inline`, it means "if multiple cpp files include this header, there is just one single `make_sound` variable, not one for each cpp file". Wait, does `constexpr` not imply `inline`? Sometimes it does, but not for variables. --- # Step by step (5) ```cpp namespace zoo { namespace _make_sound_impl { auto make_sound(/*function parameters here*/) { // some actual implementation } struct make_sound_t { auto operator()(/*function parameters here*/) const { make_sound(/*pass function parameters here*/); } }; // struct make_sound_t } // namespace _make_sound_impl inline constexpr _make_sound_impl::make_sound_t make_sound{}; } // namespace zoo ``` Move the functor in an implementation/"hidden" namespace, then call an implementation function with the same name as the global function object. We've got a niebloid here. --- # More complete example Goal recap: you write an `zoo` library and provide this customizable `zoo::make_sound` function. You want this to only apply to animals, with a default implementation for birds. --- ```cpp // library namespace namespace zoo { // Concepts for animal and bird template
concept is_animal = requires(const T& t) { t.breathe_eat_sleep(); }; template
concept is_bird = is_animal
&& requires(const T& t) { t.flap(); }; ``` --- ```cpp // "hidden" namespace namespace _make_sound_impl { // Provide default implementation for birds template
auto make_sound(const T& t) { t.flap(); } // The customization point global object type struct make_sound_t { // Constrain to animals only template
// It's a functor auto operator()(const T& t) const { make_sound(t); } }; // struct make_sound_t } // namespace _make_sound_impl // The customization point global object inline constexpr _make_sound_impl::make_sound_t make_sound{}; } // namespace zoo ``` --- # Usage of the example --- # Accept default implementation ```cpp namespace pet { struct budgie { // budgie is a bird void breathe_eat_sleep() const {} void flap() const { std::println("Flap!"); } }; } // namespace pet void test_budgie() { pet::budgie x; // default implementation of make_sound // for birds calls flap() zoo::make_sound(x); // Flap! } ``` --- # Customize, skip default ```cpp namespace pet { struct duck { // duck is a bird void breathe_eat_sleep() const {} void flap() const { std::println("Swoosh!"); } }; // custom implementation, despite being a bird void make_sound(const duck&) { std::println("Quack!"); } } // namespace pet void test_duck() { pet::duck x; zoo::make_sound(x); // Quack! } ``` --- # Error: default not available ```cpp namespace pet { struct cat { // not a bird, just an animal void breathe_eat_sleep() const {} void purr() const {} }; } // namespace pet void test_cat() { pet::cat x; // Error: because 'pet::cat' does not satisfy 'is_bird' // or // Error: no matching function for call to 'make_sound' zoo::make_sound(x); } ``` --- # Provide implementation, no default ```cpp namespace pet { struct dog { // not a bird, just an animal void breathe_eat_sleep() const {} void chase() const {} }; // but provides custom make_sound void make_sound(const dog&) { std::println("Woof!"); } } // namespace pet void test_dog() { pet::dog x; zoo::make_sound(x); // Woof! } ``` --- # Error: constraints not satisfied ```cpp namespace pet { struct butterfly { // not even an animal void flap() const { std::println("Flip!"); } }; } // namespace pet void test_butterfly() { pet::butterfly x; // Error: because 'pet::butterfly' does not satisfy 'is_animal' zoo::make_sound(x); } ``` --- # Rationale (1) - removes the need for the two step usage - Q: why do we care about `swap`? - A: the same applies to `begin`, `end` etc. - allows enforcement of concepts - one downside: unusual code that's not easy for the untrained eye to confirm it's correct (this brings memories of [Why Not Specialize Function Templates?](http://www.gotw.ca/publications/mill17.htm) --- # Rationale (2) ```cpp namespace std { template< /* ... */ > bool all_of( InputIt first, InputIt last, UnaryPredicate p ); } namespace std::ranges { template< /* ... */ > constexpr bool all_of( I first, S last, Pred pred, Proj proj = {} ); } // somewhere, calls which one? using namespace std::ranges; bool result = all_of(iter1, iter2, pred); ``` The problem is that there are two `all_of` that could work (e.g. but one expects both iterators to be the same, the other accepts a sentinel for `last`). --- # Rationale (2) ```cpp namespace std { template< /* ... */ > bool all_of( InputIt first, InputIt last, UnaryPredicate p ); } namespace std::ranges { template< /* ... */ > struct __all_of_fn { /* operator() here */ }; inline constexpr __all_of_fn all_of{}; } // somewhere, calls ranges::all_of using namespace std::ranges; bool result = all_of(iter1, iter2, pred); ``` The technique of global function object can be used independently of trying to have a customization point Helped with ranges vs. algorithm disambiguation (in effect disabling ADL, because ADL only applies to functions, not variables) --- # But As a customization point, it requires reserving global names e.g. `swap`, `begin`, `end` etc. The sender/receiver made heavy use of customization points. What to do: one choice is to combine with another technique, the usage of tags --- # Tags Classic technique to achieve different implementations, the canonical case is used in conjunction with iterators where different algorithms are available depending on the capabilities of the iterator type --- # Tags: empty classes ```cpp namespace std { struct input_iterator_tag { }; struct output_iterator_tag { }; struct forward_iterator_tag: public input_iterator_tag { }; struct bidirectional_iterator_tag: public forward_iterator_tag { }; struct random_access_iterator_tag: public bidirectional_iterator_tag { }; } ``` Tags are empty `struct`s. Note `random_access_iterator_tag` derives from `bidirectional_iterator_tag` --- # Tags: traits give tag ```cpp template
struct iterator_traits
> { using value_type = T; // ... and the tag using iterator_category = bidirectional_iterator_tag; }; ``` The traits give additional information about a type without having to modify the type itself. This example also uses [class template specialization](http://www.gotw.ca/publications/mill17.htm). Again, one can mix and match techniques. --- # Tags: dispatch based on the tag ```cpp // user calls this template
inline void evolve(BidirectionalIterator first, BidirectionalIterator last) { evolve(first, last, typename iterator_traits
::iterator_category{}); } template
inline void evolve(BidirectionalIterator first, BidirectionalIterator last, bidirectional_iterator_tag) { // more generic, but less efficient algorithm } template
inline void evolve(RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag) { // more efficient, but less generic algorithm } ``` Note advanced usage of tags that also takes advantage of tag inheritance for selection of more specialized algorithm. --- # tag_invoke The goal is to avoid an ever growing number of globally reserved names such as `swap`, `begin`, `end` etc. for customization points. So it starts by reserving a single, oddly named (to avoid conflicts in existing code): `tag_invoke`. --- # tag_invoke ```cpp namespace std { struct tag_invoke_t { template
auto operator()(Args&&... args) const { tag_invoke(std::forward
(args)...); } }; inline constexpr tag_invoke_t tag_invoke{}; } // namespace std ``` NOTE: make believe code, `std::tag_invoke` is not actually in the standard --- # More complete example Goal recap: you write an zoo library and provide this customizable zoo::make_sound function. You want this to only apply to animals, with a default implementation for birds. But this time with tag_invoke --- ```cpp // library namespace namespace zoo { // Concepts for animal and bird template
concept is_animal = requires(const T& t) { t.breathe_eat_sleep(); }; template
concept is_bird = is_animal
&& requires(const T& t) { t.flap(); }; ``` --- ```cpp // The customization point global object type struct make_sound_t { // Provide default implementation for birds template
friend auto tag_invoke(make_sound_t, const T& t) { t.flap(); } // Constrain to animals only template
// It's a functor auto operator()(const T& t) const { std::tag_invoke(*this, t); } }; // struct make_sound_t // The customization point global object inline constexpr make_sound_t make_sound{}; } // namespace zoo ``` --- # Usage of the example But this time with tag_invoke --- # Accept default implementation ```cpp namespace pet { struct budgie { // budgie is a bird void breathe_eat_sleep() const {} void flap() const { std::println("Flap!"); } }; } // namespace pet void test_budgie() { pet::budgie x; // default implementation of make_sound // for birds calls flap() zoo::make_sound(x); // Flap! } ``` --- # Customize, skip default ```cpp namespace pet { struct duck { // duck is a bird void breathe_eat_sleep() const {} void flap() const { std::println("Swoosh!"); } }; // custom implementation, despite being a bird void tag_invoke(zoo::make_sound_t, const duck&) { std::println("Quack!"); } } // namespace pet void test_duck() { pet::duck x; zoo::make_sound(x); // Quack! } ``` --- # Error: default not available ```cpp namespace pet { struct cat { // not a bird, just an animal void breathe_eat_sleep() const {} void purr() const {} }; } // namespace pet void test_cat() { pet::cat x; // Error: but message is long zoo::make_sound(x); } ``` --- # Provide implementation, no default ```cpp namespace pet { struct dog { // not a bird, just an animal void breathe_eat_sleep() const {} void chase() const {} }; // but provides custom make_sound void tag_invoke(zoo::make_sound_t, const dog&) { std::println("Woof!"); } } // namespace pet void test_dog() { pet::dog x; zoo::make_sound(x); // Woof! } ``` --- # Error: constraints not satisfied ```cpp namespace pet { struct butterfly { // not even an animal void flap() const { std::println("Flip!"); } }; } // namespace pet void test_butterfly() { pet::butterfly x; // Error: but message is long zoo::make_sound(x); } ``` --- # tag_invoke But there are downsides. One of which is that there are lots of `tag_invoke` overloads, which at least makes compilation error diagnostics very difficult for anything but the most trivial examples. --- # Future What to do? [P2547R1: Language Support for Customisable Functions](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2547r1.html) Maybe a language construct. This is similar to the move semantics that could be done pre-C++11 (but almost nobody did because of the complexity involved). --- # Bibliography - [Eric Niebler, Customization Point Design in C++11 and Beyond, 2014-10-21](https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/) - [Eric Niebler, Suggested Design for Customization Points, 2015-03-11](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html) - [Barry Revzin: Why tag_invoke is not the solution I want](https://brevzin.github.io/c++/2020/12/01/tag-invoke/) - [P2547R1: Language Support for Customisable Functions](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2547r1.html) --- # Questions?