c++tag-invokeniebloid

Why does tag_invoke pattern need the Niebloid std::tag_invoke at all?


This question assumes familiarity with the customization point management technique tag_invoke, introduced in P1895R0.

A customization point object can be defined according to P1895R0 as:

inline constexpr struct foo_cpo {
    // simplified original by omitting noexcept forward and using auto arg
    auto operator()(auto const &x) -> decltype( std::tag_invoke(*this, x) ) {
        return std::tag_invoke(*this, x); // <--^-- here are the Niebloid
    }
} foo;

But given the crux of this technique is to work with objects directly, and delegate any and all ADL to one and only agreed-upon identifier tag_invoke, then it seems the same effects can be achieved by simply,

inline constexpr struct {
    auto operator()(auto const &x) -> decltype( tag_invoke(*this, x) ) {
        return tag_invoke(*this, x); // no Niebloid. directly ADL call tag_invoke
    }
} foo;

For instance, the type erasure example from P1895R0, which is https://godbolt.org/z/3TvO4f, can be reimplemented without using the Niebloid at all: https://godbolt.org/z/n3hsqMxs7. The code is the same as the original verbatim, modulo the definition of the Niebloid std::tag_invoke and using the above ADL form for all customization points objects.

What is the requirement that the presence of Niebloid really satisfies for tag_invoke?


Solution

  • I don't think it's strictly necessary for tag_invoke itself to be a function object. But defining it as an object gives us a handy place to put a poison-pill overload should we decide that's necessary. And it's generally nice to have functions as first-class citizens that can be passed to higher-order functions. That's it, really.