什么是自定义点对象以及如何使用它们?
What are customization point objects and how to use them?
最后的c++标准草案引入了所谓的"customization point objects"([customization.point.object]),
被范围库广泛使用。
我似乎明白他们提供了一种编写begin
、swap
、data
等自定义版本的方法,它们是
由 ADL 的标准库找到。对吗?
这与以前用户定义重载的做法有何不同begin
适合她自己的类型
命名空间?特别是,为什么它们是 objects?
What are customization point objects?
它们是命名空间 std
中的函数对象实例,实现两个目标:首先 无条件触发(概念化的)参数类型要求,然后 分派到命名空间 std
或通过 ADL 中的正确函数。
In particular, why are they objects?
有必要绕过第二个查找阶段,该阶段会通过 ADL 直接引入用户提供的功能(这应该 推迟 按设计)。详情见下文。
... and how to use them?
开发应用程序时:您通常不会。这是一个标准库功能,它将向未来的自定义点添加概念检查,希望得到例如当您弄乱模板实例化时,会出现清晰的错误消息。但是,对这样的自定义点进行合格的调用,您就可以直接使用它。这是一个符合设计的假想 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的总结)
让我试着消化一下标准中这一部分背后的提案。标准库使用的 "classical" 自定义点存在两个问题。
它们很容易出错。例如,在通用代码中交换对象应该是这样的
template<class T> void f(T& t1, T& t2)
{
using std::swap;
swap(t1, t2);
}
但是对 std::swap(t1, t2)
进行合格调用太简单了 - 用户提供的
swap
永远不会被调用(见
N4381、动机和范围)
更严重的是,无法集中(概念化)传递给此类用户提供函数的类型的约束(这也是此主题在 C++20 中变得重要的原因)。再次
来自 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
,
这是需要的。调用不合格会怎样,再次参考原论文:
In the case that begin is called unqualified after bringing std::begin
into scope, the situation
is different. In the first phase of lookup, the name begin will resolve to the global object
std::begin
. Since lookup has found an object and not a function, the second phase of lookup is not
performed. In other words, if std::begin
is an object, then using std::begin; begin(a);
is
equivalent to std::begin(a);
which, as we’ve already seen, does argument-dependent lookup on the
users’ behalf.
这样,可以在 std
命名空间的函数对象中进行概念检查,
在 执行对用户提供的函数的 ADL 调用之前。没有办法绕过这个。
"Customization point object" 有点用词不当。许多 - 可能是大多数 - 实际上并不是定制点。
像 ranges::begin
、ranges::end
和 ranges::swap
这样的东西是 "true" CPO。调用其中一个会导致一些复杂的元编程发生,以确定是否有有效的自定义 begin
或 end
或 swap
可以调用,或者是否应该使用默认实现,或者如果呼叫应该是格式错误的(以 SFINAE 友好的方式)。因为许多库概念是根据有效的 CPO 调用定义的(如 Range
和 Swappable
),正确约束的通用代码必须使用此类 CPO。当然,如果您知道具体类型以及从中获取迭代器的另一种方法,请随意。
像 ranges::cbegin
这样的东西是没有 "CP" 部分的 CPO。他们总是做默认的事情,所以这不是一个定制点。同样,范围适配器对象是 CPO,但它们没有任何可定制的内容。将它们归类为 CPO 更多是为了一致性(对于 cbegin
)或规范便利性(适配器)。
最后,像 ranges::all_of
这样的东西是准 CPO 或 niebloids。它们被指定为函数模板,具有特殊的神奇 ADL 阻止属性和狡猾的措辞,以允许它们作为函数对象来实现。这主要是为了防止当 std::ranges
中的受限算法被称为不合格时,ADL 在命名空间 std
中拾取不受约束的重载。因为 std::ranges
算法接受迭代器-哨兵对,所以它通常不如它的 std
算法专门化,因此失去重载解析。
最后的c++标准草案引入了所谓的"customization point objects"([customization.point.object]), 被范围库广泛使用。
我似乎明白他们提供了一种编写begin
、swap
、data
等自定义版本的方法,它们是
由 ADL 的标准库找到。对吗?
这与以前用户定义重载的做法有何不同begin
适合她自己的类型
命名空间?特别是,为什么它们是 objects?
What are customization point objects?
它们是命名空间 std
中的函数对象实例,实现两个目标:首先 无条件触发(概念化的)参数类型要求,然后 分派到命名空间 std
或通过 ADL 中的正确函数。
In particular, why are they objects?
有必要绕过第二个查找阶段,该阶段会通过 ADL 直接引入用户提供的功能(这应该 推迟 按设计)。详情见下文。
... and how to use them?
开发应用程序时:您通常不会。这是一个标准库功能,它将向未来的自定义点添加概念检查,希望得到例如当您弄乱模板实例化时,会出现清晰的错误消息。但是,对这样的自定义点进行合格的调用,您就可以直接使用它。这是一个符合设计的假想 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的总结)
让我试着消化一下标准中这一部分背后的提案。标准库使用的 "classical" 自定义点存在两个问题。
它们很容易出错。例如,在通用代码中交换对象应该是这样的
template<class T> void f(T& t1, T& t2) { using std::swap; swap(t1, t2); }
但是对
std::swap(t1, t2)
进行合格调用太简单了 - 用户提供的swap
永远不会被调用(见 N4381、动机和范围)更严重的是,无法集中(概念化)传递给此类用户提供函数的类型的约束(这也是此主题在 C++20 中变得重要的原因)。再次 来自 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 usesstd::begin
idiomatically:
using std::begin;
begin(a);
If the call to begin dispatches to a user-defined overload, then the constraint onstd::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
,
这是需要的。调用不合格会怎样,再次参考原论文:
In the case that begin is called unqualified after bringing
std::begin
into scope, the situation is different. In the first phase of lookup, the name begin will resolve to the global objectstd::begin
. Since lookup has found an object and not a function, the second phase of lookup is not performed. In other words, ifstd::begin
is an object, thenusing std::begin; begin(a);
is equivalent tostd::begin(a);
which, as we’ve already seen, does argument-dependent lookup on the users’ behalf.
这样,可以在 std
命名空间的函数对象中进行概念检查,
在 执行对用户提供的函数的 ADL 调用之前。没有办法绕过这个。
"Customization point object" 有点用词不当。许多 - 可能是大多数 - 实际上并不是定制点。
像 ranges::begin
、ranges::end
和 ranges::swap
这样的东西是 "true" CPO。调用其中一个会导致一些复杂的元编程发生,以确定是否有有效的自定义 begin
或 end
或 swap
可以调用,或者是否应该使用默认实现,或者如果呼叫应该是格式错误的(以 SFINAE 友好的方式)。因为许多库概念是根据有效的 CPO 调用定义的(如 Range
和 Swappable
),正确约束的通用代码必须使用此类 CPO。当然,如果您知道具体类型以及从中获取迭代器的另一种方法,请随意。
像 ranges::cbegin
这样的东西是没有 "CP" 部分的 CPO。他们总是做默认的事情,所以这不是一个定制点。同样,范围适配器对象是 CPO,但它们没有任何可定制的内容。将它们归类为 CPO 更多是为了一致性(对于 cbegin
)或规范便利性(适配器)。
最后,像 ranges::all_of
这样的东西是准 CPO 或 niebloids。它们被指定为函数模板,具有特殊的神奇 ADL 阻止属性和狡猾的措辞,以允许它们作为函数对象来实现。这主要是为了防止当 std::ranges
中的受限算法被称为不合格时,ADL 在命名空间 std
中拾取不受约束的重载。因为 std::ranges
算法接受迭代器-哨兵对,所以它通常不如它的 std
算法专门化,因此失去重载解析。