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

identify niebloids #564

Open
alandefreitas opened this issue Apr 10, 2024 · 1 comment
Open

identify niebloids #564

alandefreitas opened this issue Apr 10, 2024 · 1 comment
Assignees
Labels
Feature Something new that it should do

Comments

@alandefreitas
Copy link
Collaborator

alandefreitas commented Apr 10, 2024

Motivation

Many libraries, such as Boost.URL and Boost.Buffers include Niebloids. For instance:

https://github.com/cppalliance/buffers/blob/develop/include/boost/buffers/algorithm.hpp#L182

https://github.com/boostorg/url/blob/6c48a8c4e133d3b8d5b7113e6d8cae0ac48293cd/include/boost/url/grammar/digit_chars.hpp#L78 (The pair digit_chars/digit_chars_t is a niebloid if find_if/find_if_not "doesn't count" because they're considered private)

Definition

MrDocs needs a strategy or feature to support documenting Niebloids. From the standard:

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, they inhibit argument-dependent name lookup.

void foo() {
using namespace std::ranges;
std::vector vec{1,2,3};
find(begin(vec), end(vec), 2); // #1
}
The function call expression at #1 invokes std​::​ranges​::​find, not std​::​find, despite that (a) the iterator type returned from begin(vec) and end(vec) may be associated with namespace std and (b) std​::​find is more specialized ([temp.func.order]) than std​::​ranges​::​find since the former requires its first two parameters to have the same type.

Eric Niebler himself suggested the name and wrote an article in 2014 explaining this concept.

Cppreference describes it as

The function-like entities described on this page are niebloids, that is:

  • Explicit template argument lists may not be specified when calling any of them.
  • None of them is visible to argument-dependent lookup.
  • When one of them is found by normal unqualified lookup for the name to the left of the function-call operator, it inhibits argument-dependent lookup.

Properties of Niebloids

An inline variable pair and a functor are considered a niebloid if they have the following properties:

... a CPO is an object (not a function); it’s callable; it’s constexpr-constructible, [...] it’s customizable (that’s what it means to “interact with program-defined types”); and it’s concept-constrained.
[...]
If you remove the adjectives “customizable, concept-constrained” from the above, then you have a function object that turns off ADL — but is not necessarily a customization point. The C++2a Ranges algorithms, such as std::ranges::find, are like this. Any callable, constexpr-constructible object is colloquially known as a “niebloid,” in honor of Eric Niebler.

MrDocs can use these properties to attempt to automatically identify Niebloids.

Niebloids in cppreference

Cppreference also documents niebloids as it own thing (not as class/struct or function overloads):

image

When we look at the documentation of these niebloids, the documentation is very similar to function overloads where the function names come from the inline variable and the function arguments and return type come from the functor. Pages documenting niebloids usually end with:

image

It's important to note the struct defining the niebloid and the constexpr inline variable are aggregated and documented as the same function-like entity (the niebloid). There's no individual page for the variable and for the functor.

Niebloids in Doxygen

Libraries such as range-v3 attempt to document niebloids as function overloads but the code is a mess.

https://github.com/ericniebler/range-v3/blob/53c40dd628450c977ee1558285ff43e0613fa7a9/include/range/v3/algorithm/copy.hpp#L40

It depends on lots of macros so that these symbols are identified as classes by the compiler and identified as function overloads by doxygen. The code becomes very hard to read.

Proposed solutions

Neither doxygen nor mrdocs currently have any feature to make that easier. I think niebloids are going to be the first feature where we can confidently say mrdocs does something doxygen can’t do.

MrDocs could implement a combination of the following solutions:

  • When MrDocs builds the corpus, as a post-processing step, it would aggregate pairs of variables and functors that form Niebloids according to the properties above (constexpr inline variable + constexpr-constructible callable object whose all public members are overloads of operator()). A combination of these properties is very unlikely to be a false positive.
  • This automatic detection could be disabled by a config option, in which case a special command would need to be included in the inline variable as a hint to identify it as a Niebloid. Although automatic detection should work most of the time (considering how specific the properties of Niebloids are), disabling automatic detection might be useful when the difference between a regular functor and a Niebloid might be a difference in intentionality. For instance, std::hash<T> shouldn't be documented as a niebloid even if it's a constexpr-constructible callable object whose all public members are overloads of operator() and there's a constexpr inline variable that instantiates it somewhere else.
  • These pairs of inline variables and objects could be moved to a function overload set with an extra flag to identify it as a Niebloid or to a new Info type for Niebloids. The name of the object will come from the inline variable. In any case, the type should be compatible with function overload sets. The new Info type might inherit from the type for function overload sets.
  • The documentation template pages for niebloids should include a message similar to the one in cppreference. Everything else would look similar to functions and function overload sets.

This is a general idea. Considering the complexity of the problem, implementation requirements can only be completely identified when we start working on it. We will certainly find more obstacles.

@alandefreitas alandefreitas added the Feature Something new that it should do label Apr 10, 2024
@Quuxplusone
Copy link

That's an excellent writeup IMHO. :) Your point about how std::hash<T> shouldn't be documented as a niebloid (even if some rando creates an template<class T> inline constexpr auto myHash = std::hash<T>() in another file) is important and non-obvious.

Further heuristics might include:

  • If the inline constexpr variable is not in the same file as the class definition, it's probably not a niebloid. (This is very fragile, but true of all STL vendors' house styles AFAIK.)
  • If the class type's name involves leading underscores, it could be a niebloid.
  • If the class type is nested within exactly one more non-inline namespace than the variable, it could be a niebloid.
  • If the variable (template) has a template parameter list that differs from the class (template)'s, it's probably not a niebloid. (E.g. inline constexpr auto myHashInt = std::hash<int>() doesn't qualify, because it indicates that there must be other clients of hash<T> elsewhere.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Something new that it should do
Projects
Status: No status
Development

No branches or pull requests

4 participants