什么是定制点对象以及如何使用它们?

54
[customization.point.object]),这些对象被ranges库广泛使用。

我似乎理解它们提供了一种编写自定义版本的 beginswapdata 等函数的方式,这些函数通过ADL由标准库找到。这是正确的吗?

与用户在其自己的命名空间中为其类型定义 begin 的重载之前的做法有何不同?特别是,它们为什么是对象


5
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html - cpplearner
2个回答

56
什么是定制点对象?
它们是在命名空间std中的函数对象实例,具有两个目标:首先,在参数上无条件触发(概念化的)类型要求,然后通过命名空间std或通过ADL分派到正确的函数。
特别地,为什么它们是对象?
这是为了规避第二个查找阶段,该阶段将通过ADL直接引入用户提供的函数(这应该被设计延迟)。有关更多详细信息,请参见下文。
... 如何使用它们?
在开发应用程序时,您主要不需要。这是一个标准库功能,它将向未来的自定义点添加概念检查,希望在您混淆模板实例化时产生清晰的错误消息。但是,通过对此类自定义点进行限定调用,您可以直接使用它。以下是一个符合设计要求的虚构std :: customization_point对象的示例:
namespace a {
    struct A {};
    // Knows what to do with the argument, but doesn't check type requirements:
    void customization_point(const A&);
}

// Does concept checking, then calls a::customization_point via ADL:
std::customization_point(a::A{});

目前使用例如std::swapstd::begin等功能还无法实现此操作。

说明 (摘自N4381)

让我尝试解释一下标准库中这一部分背后的提案。有两个问题与标准库使用的“经典”定制点相关。

  • They are easy to get wrong. As an example, swapping objects in generic code is supposed to look like this

    template<class T> void f(T& t1, T& t2)
    {
        using std::swap;
        swap(t1, t2);
    }
    

    but making a qualified call to std::swap(t1, t2) instead is too simple - the user-provided swap would never be called (see N4381, Motivation and Scope)

  • More severely, there is no way to centralize (conceptified) constraints on types passed to such user provided functions (this is also why this topic gained importance with C++20). Again from N4381:

    Suppose that a future version of std::begin requires that its argument model a Range concept. Adding such a constraint would have no effect on code that uses std::begin idiomatically:

    using std::begin;
    begin(a);

    If the call to begin dispatches to a user-defined overload, then the constraint on std::begin has been bypassed.

该提案所描述的解决方案通过以下方式缓解了两个问题,实现类似于std::begin的虚构实现。

namespace std {
    namespace __detail {
        /* Classical definitions of function templates "begin" for
           raw arrays and ranges... */

        struct __begin_fn {
            /* Call operator template that performs concept checking and
             * invokes begin(arg). This is the heart of the technique.
             * Everyting from above is already in the __detail scope, but
             * ADL is triggered, too. */

        };
    }

    /* Thanks to @cpplearner for pointing out that the global
       function object will be an inline variable: */
    inline constexpr __detail::__begin_fn begin{}; 
}

首先,对于例如std::begin(someObject)的合格调用始终会通过std::__detail::__begin_fn进行绕路,这是期望的。对于未经限定的调用发生了什么,我再次参考原始论文:

在将std::begin引入作用域后对未经限定的调用begin的情况下,情况就不同了。在查找的第一阶段中,名称begin将解析为全局对象std::begin。由于查找已经找到了一个对象而不是函数,因此不会执行查找的第二阶段。换句话说,如果std::begin是一个对象,则using std::begin; begin(a);等同于std::begin(a);,如我们已经看到的那样,代表用户的依赖查找。

这样,可以在std命名空间中的函数对象内执行概念检查,在执行用户提供的函数的ADL调用之前进行。没有办法规避这种情况。

8
请注意,C++17内联变量使ODR技巧无效。现在,inline constexpr __detail::__begin_fn begin{};即可。 - cpplearner
1
关于 Eric Niebler 的草案。他在这里发表了一篇关于自定义点的绝妙博客文章:http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/ - AndyG
3
如果我没有记错的话,在std::中直接没有CPOs。 - T.C.
1
如果我没错的话,像 std::begin 这样的自定义点仍然是自由函数,而不是函数对象,对吧?唯一作为函数对象实现的自定义点是来自范围库的那些,例如 std::ranges::begin - ABu
2
@Peregring-lk 我也这么认为,否则这将会破坏向后兼容性。 - lubgr
显示剩余8条评论

22

“自定义点对象”这个术语有些不准确。很多(可能大多数)实际上并不是自定义点。

例如 ranges::beginranges::endranges::swap 是“真正的” CPO。调用其中之一会导致一些复杂的元编程发生,以确定是否存在有效的自定义 beginendswap 可以调用,或者是否应该使用默认实现,或者是否应该将调用作为SFINAE中的无效形式。因为许多库概念是根据CPO调用是否有效来定义的(例如 RangeSwappable),因此正确约束的通用代码必须使用此类 CPO。当然,如果您知道具体类型和另一种获取其迭代器的方法,则随意使用其他方法。

例如 ranges::cbegin 是没有“CP”部分的 CPO。它们始终执行默认操作,因此没有太多的自定义空间。同样,范围适配器对象是 CPO,但它们并没有可定制化的内容。将它们归类为 CPO 更多的是为了保持一致性(对于 cbegin)或规范上的方便(适配器)。

最后,像ranges::all_of这样的东西是准CPO或niebloids。它们被指定为具有特殊魔术ADL阻塞属性的函数模板,并使用诡辩措辞以便可以实现为函数对象。这主要是为了防止未限制调用std::ranges中的约束算法时,ADL选择命名空间std中的非受约束重载。因为std::ranges算法接受迭代器-哨兵对,通常比其std对应项更不专业化,结果失去了重载分辨率。


6
ranges::dataranges::sizeranges::empty是C++20中的范围库函数,它们都是CPO(自定义操作符)的变体,并且被定义为“真正”的CPO。 - metalfox
5
是的,它们实际上是可定制的。 - T.C.

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