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

met*_*fox 19 c++ c++20

c ++标准的最后一个草案引入了所谓的"自定义点对象"([customization.point.object]),它们被范围库广泛使用.

我似乎明白,他们提供一种方式来编写的定制版本begin,swap,data等,这是由ADL标准库中找到.那是对的吗?

这与用户begin在她自己的命名空间中为她的类型定义重载的先前实践有何不同?特别是,他们为什么反对

lub*_*bgr 18

什么是自定义点对象?

它们是命名空间中的函数对象实例,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{});
Run Code Online (Sandbox Code Playgroud)

这是目前不可能如std::swap,std::begin等.

说明(N4381的摘要)

让我试着消化标准中这一部分背后的提议.标准库使用的"经典"自定义点有两个问题.

  • 他们很容易出错.例如,在通用代码中交换对象应该看起来像这样

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

    但是进行合格的调用std::swap(t1, t2)太简单了 - swap永远不会调用用户提供的 (参见 N4381,Motivation和Scope)

  • 更严重的是,没有办法将(受限制的)约束集中在传递给这些用户提供的函数的类型上(这也是为什么这个主题在C++ 20中获得重要性的原因).再次来自N4381:

    假设未来版本std::begin要求其参数模型为Range概念.添加这样的约束对于以std::begin惯用方式使用的代码没有影响:

    using std::begin;
    begin(a);

    如果开始调用调度到用户定义的重载,则std::begin 绕过约束.

提案中描述的解决方案通过以下方法,虚构实现来减轻这两个问题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{}; 
}
Run Code Online (Sandbox Code Playgroud)

首先,合格的呼叫,例如std::begin(someObject)总是绕道而行std::__detail::__begin_fn,这是所希望的.对于不合格的电话会发生什么,我再次参考原始论文:

如果在std::begin进入范围后开始被称为不合格,情况就不同了.在查找的第一阶段,名称begin将解析为全局对象 std::begin.由于查找找到了对象而不是函数,因此不执行第二阶段的查找.换句话说,如果std::begin是一个对象,那么using std::begin; begin(a);就相当于std::begin(a);,正如我们已经看到的,确实代表了用户的参数相关的查找.

这样,在执行对用户提供的函数的ADL调用之前,可以在std命名空间中 的函数对象内执行概念检查.没有办法绕过这个.

  • 请注意,ODR技巧是由C++ 17内联变量实现的.现在`inline constexpr __detail :: __ begin_fn begin {};`应该足够了. (5认同)
  • 直接在`std::` 中没有 CPO,IIRC。 (2认同)
  • @Peregring-lk 我也这么认为,否则这会破坏向后兼容性。 (2认同)

T.C*_*.C. 9

"定制点对象"有点用词不当.许多人 - 可能是大多数人 - 实际上并不是定制点.

之类的东西ranges::begin,ranges::end以及ranges::swap是"真"纲要.调用这些原因之一一些复杂的元编程来发生弄清楚是否有一个有效的定制beginendswap打电话,或如果默认的实现应该使用,或者该呼叫是否应改为形成不良的(在SFINAE友好方式).由于许多库概念是根据CPO调用有效(如RangeSwappable)定义的,因此正确约束的通用代码必须使用此类CPO.当然,如果你知道具体类型和另一种获取迭代器的方法,请随意.

ranges::cbegin没有"CP"部分的CPO 就是这样.他们总是做默认的事情,所以它不是一个定制点.同样,范围适配器对象是CPO,但没有任何可定制的对象.将它们分类为CPO更多的是一致性(for cbegin)或规范便利性(适配器).

最后,像ranges::all_ofCPO或niebloids这样的东西.它们被指定为具有特殊魔法ADL阻塞属性的函数模板和weasel措辞,以允许它们作为函数对象实现.这主要是为了防止ADL在std约束算法in std::ranges被称为不合格时获取命名空间中的无约束重载.因为该std::ranges算法接受迭代器 - 哨兵对,所以它通常不如其std对应物那么专业化并因此失去过载分辨率.

  • `ranges::data`、`ranges::size` 和 `ranges::empty` 怎么样?他们是“真正的”CPO吗? (4认同)
  • 是的,那些实际上是可定制的. (2认同)