c ++标准的最新草案介绍了所谓的“定制点对象”([customization.point.object]), 范围库广泛使用了
我似乎理解它们提供了一种编写begin
,swap
,data
等自定义版本的方法。
由ADL在标准库中找到。正确吗?
这与以前的做法有何不同,在以前的做法中,用户定义了例如begin
代表自己的类型
命名空间?特别是为什么它们对象?
答案 0 :(得分:14)
什么是定制点对象?
它们是名称空间std
中的函数对象实例,可以实现两个目标: first 无条件触发(自定义)参数的类型要求,然后然后通过名称空间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::swap
,std::begin
等。
让我尝试摘要了解本部分中的提案。标准库使用“经典”定制点有两个问题。
它们很容易出错。例如,交换通用代码中的对象应该看起来像这样
template<class T> void f(T& t1, T& t2)
{
using std::swap;
swap(t1, t2);
}
,但是对std::swap(t1, t2)
进行限定调用太简单了-用户提供
swap
将永远不会被调用(请参阅
N4381,动机和范围)
更严重的是,没有办法集中(传递给传递给此类用户提供的函数的类型的约束)(这也是为什么该主题在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{};
}
首先,对例如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调用。没有办法避免这种情况。
答案 1 :(得分:6)
“定制点对象”有点用词不当。许多(可能是多数)实际上并不是定制点。
诸如ranges::begin
,ranges::end
和ranges::swap
之类的都是“真实” CPO。调用其中之一会导致进行一些复杂的元编程,以弄清楚是否存在要调用的有效的自定义begin
或end
或swap
,或者是否应使用默认实现,或者呼叫是否格式错误(以SFINAE友好的方式)。因为根据有效的CPO调用定义了许多库概念(例如Range
和Swappable
),所以正确约束的通用代码必须使用此类CPO。当然,如果您知道具体的类型以及从中获得迭代器的另一种方法,请放心。
诸如ranges::cbegin
之类的东西是没有“ CP”部分的CPO。他们总是做默认的事情,所以这不是定制的重点。同样,范围适配器对象是CPO,但没有可自定义的对象。将它们归类为CPO主要是为了保持一致性(为了cbegin
)或规范方便(适配器)。
最后,像ranges::all_of
之类的东西是准CPO或准四倍体。它们被指定为具有特殊的神奇ADL阻止属性和狡猾的措辞的功能模板,从而可以将其实现为功能对象。这主要是为了防止在std
中的约束算法被称为不合格时,ADL在命名空间std::ranges
中拾取不受约束的重载。因为std::range
算法接受迭代器-前哨对,所以它通常不如std
对应,并且失去了重载分辨率。