什么是自定义点对象?
它们是命名空间中的函数对象实例std
实现两个目标:first无条件触发对参数的(概念化)类型要求,then分派到命名空间中正确的函数std
或通过 ADL。
特别是,为什么他们objects?
这对于绕过第二个查找阶段是必要的,该阶段将通过 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::swap
, std::begin
等等。
说明(总结N4381 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html)
让我尝试消化标准中本节背后的提案。标准库使用的“经典”定制点存在两个问题。
-
他们很容易出错。例如,在通用代码中交换对象应该如下所示
template<class T> void f(T& t1, T& t2)
{
using std::swap;
swap(t1, t2);
}
但打了一个合格的电话std::swap(t1, t2)
相反太简单了 - 用户提供的swap
永远不会被调用(参见N4381 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html、动机和范围)
-
更严重的是,没有办法集中(概念化)对传递给此类用户提供的函数的类型的约束(这也是该主题在 C++20 中变得重要的原因)。再次
从N4381 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html:
假设未来版本std::begin
要求其参数模型是 Range 概念。
添加这样的约束不会对使用的代码产生影响std::begin
惯用语:
using std::begin;
begin(a);
如果对 begin 的调用分派到用户定义的重载,则对的约束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{};
}
首先,对例如进行合格的调用std::begin(someObject)
总是绕道而行std::__detail::__begin_fn
,
这是所期望的。对于不合格调用会发生什么,我再次参考原始论文:
在带入之后 begin 被称为不合格的情况下std::begin
范围、情况
是不同的。在查找的第一阶段,名称 begin 将解析为全局对象std::begin
。由于查找找到的是对象而不是函数,因此查找的第二阶段不是
执行。换句话说,如果std::begin
是一个对象,那么using std::begin; begin(a);
是
相当于std::begin(a);
正如我们已经看到的,它对
代表用户。
这样,可以在函数对象内执行概念检查std
命名空间,before执行对用户提供的函数的 ADL 调用。没有办法规避这一点。