Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_Not_quite_object has outlived its usefulness #4097

Closed
CaseyCarter opened this issue Oct 17, 2023 · 12 comments · Fixed by #4098
Closed

_Not_quite_object has outlived its usefulness #4097

CaseyCarter opened this issue Oct 17, 2023 · 12 comments · Fixed by #4098
Labels
enhancement Something can be improved fixed Something works now, yay! ranges C++20/23 ranges

Comments

@CaseyCarter
Copy link
Contributor

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::ranges namespace 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:

STL/stl/inc/xutility

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.

@CaseyCarter CaseyCarter added enhancement Something can be improved decision needed We need to choose something before working on this ranges C++20/23 ranges labels Oct 17, 2023
@frederick-vs-ja
Copy link
Contributor

Hmm, niebloids effectively become CPOs with _Not_quite_object removed.

I found that @ericniebler once said that they should be the same. Should we have a paper to turn niebloids into Standard-guaranteed CPOs?

@CaseyCarter
Copy link
Contributor Author

Hmm, niebloids effectively become CPOs with _Not_quite_object removed.

Yes they do.

I found that @ericniebler once said that they should be the same.

I assure you he has said it many times, though I can provide no citations.

Should we have a paper to turn niebloids into Standard-guaranteed CPOs?

I would like to have such a paper, although I don't have time to write one now. I don't think a paper is a prerequisite for implementing the change in the STL.

@ericniebler
Copy link

Vindication 😉

@StephanTLavavej
Copy link
Member

I'm in favor of doing this. The user code is reasonable, and expecting Library weirdness to drive Core changes was... unrealistic. (Sometimes it happens, as with explicit(bool), but one has to come up with the necessary Core feature and then get Barry Revzin to work his drafting magic 😹)

@philnik777
Copy link

philnik777 commented Oct 17, 2023

FWIW I'm also in favour of this. Having the three big implementations agree that they should just be CPOs is really nice. Maybe we could agree to have this as an official cross-library extension like we have for C++20 std modules? CC @ldionne @jwakely

@jwakely
Copy link

jwakely commented Oct 17, 2023

I just re-read @brevzin's https://brevzin.github.io/c++/2020/12/19/cpo-niebloid/ and now you want to make it untrue?! 😂

I'm not generally in favour of "it works on two implementations so the other one must be buggy". In this case, I don't really care either way. I'm fine with making them CPOs, I'm also fine with leaving them as things with some unspecified properties.

@brevzin
Copy link

brevzin commented Oct 17, 2023

For what it's worth, even if we had some cool language facility, it's still super valuable for these to be objects to pass into algorithms.

Right now, we're in this situation where views::transform(ranges::size) is specified to be valid but views::transform(ranges::distance) is unspecified (but works on libstdc++/libc++), just because ranges::size happens to be specified as an object but ranges::distance happens to be specified as a function. That's an awkward place to be... so I think even if everyone just decided to implement a feature like [[no_adl]] to just inhibit ADL so that people can actually implement functions using functions (not sure why anybody would want to do such a thing?), having these things be objects is more functional.

@ldionne
Copy link

ldionne commented Oct 17, 2023

We had various discussions in libc++ about this. In fact we originally started with something like _Not_quite_object and eventually we gave up -- basically for the same reasons stated here. It is useful to pass those objects around and we thought that adding complexity to the library for preventing users from relying on this wasn't really worth it. We also wanted to write a paper but never got around to doing it.

So FWIW I think it is 100% reasonable for MSVC to make this change, and we should write a paper to simply officialize this in the Standard (I am not volunteering to write that paper either, though).

@CaseyCarter
Copy link
Contributor Author

For what it's worth, even if we had some cool language facility, it's still super valuable for these to be objects to pass into algorithms.

Making the niebloids into objects lets us use them easily as arguments. Making overload sets first-class lets us use everything in the standard library (and other libraries) easily as arguments and does so without the performance penalties inherent in using function objects instead of function( template)s. I would much rather that the language solve this problem for everyone but I will settle for making a few dozen niebloids be objects.

@frederick-vs-ja
Copy link
Contributor

That's an awkward place to be... so I think even if everyone just decided to implement a feature like [[no_adl]] to just inhibit ADL so that people can actually implement functions using functions (not sure why anybody would want to do such a thing?)

I think it's worthwhile to have something that merely bans ADL for functions without turning them into other things. With such a thing, library authors can avoid qualifying function calls or writing wrapping classes and function objects to suppress ADL (see also #140).

However, such a thing doesn't seem suitable for a standard attribute, since ignoring it can heavily change the semantics of a well-defined program.

@AlexGuteniev
Copy link
Contributor

However, such a thing doesn't seem suitable for a standard attribute, since ignoring it can heavily change the semantics of a well-defined program.

A keyword then, like no_adl? Or reuse some of existing, like explicit?

@SuperWig
Copy link
Contributor

Or reuse some of existing, like explicit?

So co_explicit or explicit explicit?

@StephanTLavavej StephanTLavavej removed the decision needed We need to choose something before working on this label Oct 18, 2023
@StephanTLavavej StephanTLavavej added the fixed Something works now, yay! label Oct 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Something can be improved fixed Something works now, yay! ranges C++20/23 ranges
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants