什么是niebloid?

131

随着C++20的推出,我们现在可以在cppreference中更频繁地看到“niebloid”这个术语。

在SO上,今天(2020/07/16)可以找到两篇提到它的文章:

Google也没有给出太多结果。最著名的可能是这里

有人能否更详细地说明niebloids呢?


可以在此处找到定义:https://en.cppreference.com/w/cpp/algorithm/ranges/all_any_none_of,其中`std::range::all_of`是`niebloid`的一个示例。 - JulianW
2个回答

115
术语niebloid源自Eric Niebler的名字。简单来说,它们是函数对象,会禁用ADL(argument-dependent lookup)的发生,这样当调用std::ranges的算法时,就不会选择std::中的重载函数。
以下是一条推文(来自2018年),以及Eric本人提供的答案,建议使用这个名称。Eric在2014年撰写了一篇解释这个概念的文章
最好在标准文档本身中看到它的实际应用:

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.

上面的示例已关闭 ADL,因此调用直接进入 std::ranges::find。
让我们创建一个小示例来进一步探索:
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;
}

cppreference的描述:

本页面描述的函数式实体是Niebloids,即:

  • 在调用它们时不能指定显式模板参数列表。
  • 它们中没有一个对参数相关查找可见。
  • 当通过函数调用运算符左侧的名称进行正常未限定查找找到其中一个时,它会抑制参数相关查找。

Niebloids不可见于参数相关查找(ADL),因为它们是函数对象,而ADL仅适用于自由函数而不是函数对象。第三点是标准中示例中发生的情况。

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

find()调用未限定,因此在查找开始时,它会找到std::ranges::find函数对象,这反过来阻止了ADL的发生。

继续搜索,我发现这个,在我看来是关于niebloids和CPOs(自定义点对象)最易理解的解释:

……一个CPO是一个对象(不是函数);它可被调用;它是constexpr-constructible的,[...]它是可定制的(这就是“与程序定义类型交互”的含义);并且它是概念约束的。
[...]
如果从上面删除“可定制,概念约束”这些形容词,那么你就得到了一个关闭ADL的函数对象——但不一定是一个自定义点。C++2a范围算法(例如std::ranges::find)就是这样。 任何可调用的、constexpr-constructible对象都通俗地称为“niebloid”,以纪念Eric Niebler。


12
Niebloids并非保证为对象。它们被指定为魔法函数模板,使用足够的含糊措辞来允许其被实现为对象,但不止于此。 - T.C.
47
当我阅读这样的帖子时,我意识到自己所不知道的很多。 - Galaxy
4
“ADL免疫功能”似乎是一个更好、自我描述性更强的名称。为什么不使用这个名字呢? - thakis
35
我开玩笑地提出了“niebloid”。令我失望的是,这个词被采纳了。自食其果。 - Eric Niebler
1
@EricNiebler:更恰当地说,Niebloids会禁用Koenig查找吗? - Laurent LA RIZZA
显示剩余3条评论

4

cppreference 网站上了解到:

本页面描述的函数实体是niebloids,也就是:

  • 调用它们时不能指定显式模板参数列表。

  • 它们都不可见于参数相关的查找。

  • 当通过函数调用运算符左侧的名称进行正常非限定查找时,其中之一会抑制参数相关的查找。

在实践中,它们可以作为函数对象或使用特殊的编译器扩展来实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接