-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
The Standard denotes some overload sets of function templates as having special properties which make it possible to implement them as function objects. For example [algorithms.requirements]/2:
The entities defined in the
std::rangesnamespace in this Clause are not found by argument-dependent name lookup ([basic.lookup.argdep]). When found by unqualified ([basic.lookup.unqual]) name lookup for the postfix-expression in a function call ([expr.call]), they inhibit argument-dependent name lookup. [ed: Example omitted for brevity.]
together with [algorithms.requirements]/15:
The well-formedness and behavior of a call to an algorithm with an explicitly-specified template argument list is unspecified, except where explicitly stated otherwise.
provides such properties for the algorithms implemented in namespace std::ranges. The Standard doesn't make these explicitly function objects in the hopes that C++ will eventually provide core language support for overload sets as first-class objects, mechanisms to forbid finding selected function( template)s via ADL, and mechanisms for selected function( template)s to inhibit ADL when found by normal unqualified name lookup. The fear is that allowing users to take advantage of the fact that these algorithms are implemented as function objects today may create breakage if and when they transition into overload sets of function templates using future tech to provide the desired properties.
These overload sets not guaranteed to be implemented via function objects but in practice implemented exactly so are colloquially known as niebloids, much to the chagrin of Eric Niebler who wanted them to simply be function objects until being swayed by my starry-eyed naivete (as a relatively new WG21 member) into believing we could change the language in a reasonable timeframe. The STL uses an internal type _Not_quite_object:
Lines 3220 to 3243 in 1c59a20
| class _Not_quite_object { | |
| public: | |
| // Some overload sets in the library have the property that their constituent function templates are not visible | |
| // to argument-dependent name lookup (ADL) and that they inhibit ADL when found via unqualified name lookup. | |
| // This property allows these overload sets to be implemented as function objects. We derive such function | |
| // objects from this type to remove some typical object-ish behaviors which helps users avoid depending on their | |
| // non-specified object-ness. | |
| struct _Construct_tag { | |
| explicit _Construct_tag() = default; | |
| }; | |
| _Not_quite_object() = delete; | |
| constexpr explicit _Not_quite_object(_Construct_tag) noexcept {} | |
| _Not_quite_object(const _Not_quite_object&) = delete; | |
| _Not_quite_object& operator=(const _Not_quite_object&) = delete; | |
| void operator&() const = delete; | |
| protected: | |
| ~_Not_quite_object() = default; | |
| }; |
to help define niebloids with as little object-like behavior as possible to avoid users depending on their object-ness.
Fast-forward to 2023. No core language support for niebloids has materialized, nor is likely to do so in time for C++26. Both libstdc++ and libc++ have implemented the niebloids as simple function objects. C++20 code in the wild has begun to depend on that object-ness, and the STL is beginning to receive bug reports like DevCom-10450794 for code that compiles fine with GCC and Clang (well, it would if libc++ had shipped chunk_by). Niebloids have failed both to encourage C++ core language support, and to prevent users from depending on implementation as function objects.
There seems to be no reason to keep our _Not_quite_object around; I propose that we remove it and the resulting implementation divergence.