Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a niebloid?

Tags:

c++

c++20

With C++20 we can read the term "niebloid" more often now in the cppreference.

On SO we can find today 2020/07/16 2 articles mentioning it:

  • First post
  • Second post, talking about customization point objects

Google does also not spit out that many results. The most prominent is maybe here.

Can somebody shed a little bit more light on niebloids?

like image 842
Armin Montigny Avatar asked Jul 16 '20 05:07

Armin Montigny


People also ask

What is niebloid in C++?

Niebloid is a catchy short hand named after Eric Niebler who crafted range-v3 and the Ranges TS, now officially std::ranges for C++20. It refers to a set of requirements placed on algorithm invokables.

Why are niebloids not visible in argument dependent lookup?

Niebloids aren't visible to argument dependent lookup (ADL) because they are function objects, and ADL is done only for free functions and not function objects. The third point is what happened in the example from the standard:

What is the difference between a niebloid and a customization point?

a customization point object is a semiregular, function object (by definition) that exists to handle constrained ADL dispatch for you. ranges :: begin, ranges :: swap, etc, are customization point objects. a niebloid is a colloquial name for the algorithms declared in std :: ranges that inhibit ADL.

Are niebloids the Silver Bullet of function call based customization points?

Still, niebloids were seen as the silver bullet of function-call based customization points that utilize ADL and is employed heavily in range-v3. But, there are some problems with it.


1 Answers

The term niebloid comes from Eric Niebler's name. In simple words, they are function objects that disable ADL (argument-dependent lookup) from happening so that the overloads in std:: aren't picked up when an algorithm from std::ranges is called.

Here's a tweet (from 2018) and answer from Eric himself suggesting the name. Eric wrote an article in 2014 explaining this concept.

It can best be seen in action in the standard document itself:

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

void foo() {   using namespace std::ranges;   std::vector<int> 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.

The above example has ADL turned off, so the call goes directly to std::ranges::find.

Let's create a small example to explore this further:

namespace mystd {     class B{};     class A{};     template<typename T>     void swap(T &a, T &b)     {         std::cout << "mystd::swap\n";     } }  namespace sx {     namespace impl {        //our functor, the niebloid         struct __swap {             template<typename R, typename = std::enable_if_t< std::is_same<R, mystd::A>::value >  >             void operator()(R &a, R &b) const             {                 std::cout << "in sx::swap()\n";                 // swap(a, b);              }         };     }     inline constexpr impl::__swap swap{}; }  int main() {     mystd::B a, b;     swap(a, b); // calls mystd::swap()      using namespace sx;     mystd::A c, d;     swap(c, d); //No ADL!, calls sx::swap!      return 0; } 

Description from cppreference:

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.

Niebloids aren't visible to argument dependent lookup(ADL) because they are function objects, and ADL is done only for free functions and not function objects. The third point is what happened in the example from the standard:

find(begin(vec), end(vec), 2); //unqualified call to find 

The call to find() is unqualified, so when lookup starts, it finds std::ranges::find function object which in turn stops ADL from happening.

Searching some more, I found this which, in my opinion is the most understandable explanation of niebloids and CPOs (customization point objects):

... 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.

like image 93
Waqar Avatar answered Oct 26 '22 01:10

Waqar