问题描述
来自libstdc ++ <concepts>
标头:
namespace ranges
{
namespace __cust_swap
{
template<typename _Tp> void swap(_Tp&,_Tp&) = delete;
来自MS-STL <concepts>
标头:
namespace ranges {
namespace _Swap {
template <class _Ty>
void swap(_Ty&,_Ty&) = delete;
在您要禁止调用复制/移动作业/控制器的上下文之外,我从未遇到过= delete;
。
我很好奇是否有必要,所以我像这样从库中注释掉了= delete;
部分:
// template<typename _Tp> void swap(_Tp&,_Tp&) = delete;
查看以下测试用例是否可以编译。
#include <concepts>
#include <iostream>
struct dummy {
friend void swap(dummy& a,dummy& b) {
std::cout << "ADL" << std::endl;
}
};
int main()
{
int a{};
int b{};
dummy c{};
dummy d{};
std::ranges::swap(a,b);
std::ranges::swap(c,d); // Ok. Prints "ADL" on console.
}
不仅可以编译,而且可以通过为swap
调用用户定义的struct dummy
来表现良好。所以我想知道,
-
template<typename _Tp> void swap(_Tp&,_Tp&) = delete;
在此情况下到底能做什么? - 在没有
template<typename _Tp> void swap(_Tp&,_Tp&) = delete;
的情况下此中断什么时候出现?
解决方法
TL; DR:可以避免调用std::swap
。
这实际上是对the ranges::swap
customization point的明确要求:
如果S
或(void)swap(E1,E2)
具有类或枚举类型([basic.compound])并且该表达式有效,并且在包含此定义的上下文:E1
那这是做什么的?要了解这一点,我们必须记住E2
名称空间实际上是 template<class T>
void swap(T&,T&) = delete;
名称空间。这很重要,因为ranges
命名空间中包含许多内容。包括在std::ranges
中声明的内容:
std
某处可能有<utility>
和template< class T >
void swap( T& a,T& b );
,但这与我们的需求无关。
constexpr
作为自定义点,具有希望您对其进行自定义的特定方式。它希望您提供一个noexcept
函数,可以通过依赖于参数的查找找到该函数。这意味着std::ranges::swap
将通过执行以下操作来找到交换功能:swap
。
那很好,除了一个问题:ranges::swap
存在。在C ++之前的20天里,使类型可交换的一种有效方法是为swap(E1,E2)
模板提供特殊化。因此,如果您直接致电std::swap
交换某些东西,那么您的专业将被使用。
std::swap
不想使用它们。它具有一种自定义机制,并且希望您非常肯定地使用该机制,而不是std::swap
的模板专业化。
但是,由于ranges::swap
位于std::swap
命名空间中,因此对std::ranges::swap
的不合格调用可以找到std
。为避免发现和使用此重载,它通过使swap(E1,E2)
d版本可见而毒化了重载。因此,如果您不为您的类型提供ADL可见的std::swap
,则会遇到硬错误。与= delete
版本相比,适当的自定义还需要更专业(或更受约束),以便可以将其视为更好的重载匹配。
请注意,swap
和类似函数的措辞相似,可以解决名称相似的std::swap
函数的类似问题。
毒药超载的动机有两种,其中大多数实际上已经不存在了,但无论如何我们仍然有。
交换/ iter_swap
如P0370中所述:
Ranges TS还有N4381不能解决的另一个定制点问题:Range TS的实现需要与标准库的实现共存。如果ADL会导致调用命名空间std中相同名称的定制点,则提供具有强语义约束的定制点几乎没有好处。例如,请考虑单一类型Swappable概念的定义:
namespace std { namespace experimental { namespace ranges { inline namespace v1 { template <class T> concept bool Swappable() { return requires(T&& t,T&& u) { (void)swap(std::forward<T>(t),std::forward<T>(u)); }; } }}}}
名称交换的不合格名称查找可以直接在命名空间std中找到不受约束的交换-只是几次跳到命名空间层次结构中-如果
std
是{{1}的关联命名空间,则可以通过ADL }或T
。如果U
不受限制,则该概念对于所有类型都是“满意的”,并且实际上是无用的。 Ranges TS通过要求更改std :: swap来解决此问题,这是TS历来禁止的做法。通过修改命名空间std中的定义,将类似的约束应用于TS中定义的所有自定义点,这不是一个令人满意的解决方案。
Range TS建立在C ++ 14上,其中std::swap
不受约束(std::swap
直到C ++ 17采用P0185时才受到约束),因此它确保对于将std::swap
作为关联命名空间的任何类型,Swappable
不会仅仅简单地解析为true至关重要。
但是现在std
被限制了,所以不需要std::swap
毒丸了。
但是,swap
仍然不受限制,因此有必要使用该毒丸。 但是,这样很容易受到约束,然后我们再也不需要毒药了。
开始/结束
如P0970中所述:
为了与
std::iter_swap
兼容并易于迁移,std::begin
接受了右值并将它们与const左值相同。不建议使用此行为,因为它从根本上讲是不合理的:由此类重载返回的任何迭代器在包含调用begin的完整表达式之后很可能会悬空另一个问题(直到最近才似乎与begin的设计无关)是,如果传递给它们的范围是一个返回值,则返回迭代器的算法会将这些迭代器包装在std :: experimental :: ranges :: dangling 中。右值。这忽略了以下事实:对于某些范围类型(尤其是P0789的
std::experimental::ranges::begin
),迭代器的有效性完全不取决于范围的生存期。如果将prvaluesubrange<>
传递给算法,则完全不需要返回包装的迭代器。[...]
我们认识到,通过从范围访问自定义点中删除不推荐使用的默认值对rvalue的支持,我们为范围作者提供了设计空间来选择其行为范围类型的行为,从而与迭代器可以安全地进行算法通信超出其范围类型。这样就可以避免在传递重要值
subrange<>
的情况下使用dangling
。
本文接着提出了一种设计,用于安全地调用rvalues上的subrange
作为一个非成员函数,具体来说,它是一个rvalue。存在的
begin
超载:
赋予
template <class T> void begin(T&&) = delete;
属性,对于类型为std2::begin
的某些右值表达式E
,除非有自由函数{{1},否则表达式T
不会编译。 }可被ADL查找,该ADL专门接受类型为std2::begin(E)
的右值,并且与一般的begin
“毒丸”重载相比,通过部分排序更喜欢重载。
例如,这将使我们能够正确拒绝对类型为T
的右值调用void begin(T&&)
,即使ADL将找到非成员ranges::begin
。
但是这篇论文还说:
作者认为,要解决
std::vector<int>
和std::begin(const C&)
的问题,就需要添加新的特征,以使范围类型的作者可以说出其迭代器是否可以安全地超出范围。这感觉像是一种骇人听闻的感觉,而作者无法为如此简洁明了的特征选择一个名字,进一步增强了这种感觉。
从那时起,此功能已通过特征进行检查-首先被称为subrange
(P1858),现在被称为dangling
(LWG3379)。再次重申,这里不再需要毒药。