gpt4 book ai didi

c++ - 为什么不的unique_ptr在C++ 20 equality_comparable_with nullptr_t?

转载 作者:行者123 更新时间:2023-12-04 04:27:01 26 4
gpt4 key购买 nike

使用 C++20 的 concept s 我注意到 std::unique_ptr 似乎无法满足 std::equality_comparable_with<std::nullptr_t,...> 概念。从 std::unique_ptr 的定义来看,它应该在 C++20 中实现以下内容:

template<class T1, class D1, class T2, class D2>
bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);

template <class T, class D>
bool operator==(const unique_ptr<T, D>& x, std::nullptr_t) noexcept;
这个要求应该实现与 nullptr 的对称比较——根据我的理解,这足以满足 equality_comparable_with
奇怪的是,这个问题似乎在所有主要编译器上都是一致的。以下代码被 Clang、GCC 和 MSVC 拒绝:
// fails on all three compilers
static_assert(std::equality_comparable_with<std::unique_ptr<int>,std::nullptr_t>);
Try Online
然而,与 std::shared_ptr 相同的断言被接受:
// succeeds on all three compilers
static_assert(std::equality_comparable_with<std::shared_ptr<int>,std::nullptr_t>);
Try Online
除非我误解了什么,否则这似乎是一个错误。
我的问题是这是否是三个编译器实现中的巧合错误,还是 C++20 标准中的缺陷?
注意: 我正在标记这个 以防这碰巧是一个缺陷。

最佳答案

TL;DR: std::equality_comparable_with<T, U> 要求 TU 都可以转换为 T 和 0x214134 的公共(public)引用。对于 Ustd::unique_ptr<T> 的情况,这要求 std::nullptr_t 是可复制构造的,而事实并非如此。

系好安全带。这真是一段旅程。考虑我 nerd-sniped
为什么我们不满足这个概念?
std::unique_ptr<T> 要求:

template <class T, class U>
concept equality_comparable_with =
std::equality_comparable<T> &&
std::equality_comparable<U> &&
std::common_reference_with<
const std::remove_reference_t<T>&,
const std::remove_reference_t<U>&> &&
std::equality_comparable<
std::common_reference_t<
const std::remove_reference_t<T>&,
const std::remove_reference_t<U>&>> &&
__WeaklyEqualityComparableWith<T, U>;

那是一口。将概念分解成各个部分, std::equality_comparable_with 失败 std::equality_comparable_with<std::unique_ptr<int>, std::nullptr_t> :
<source>:6:20: note: constraints not satisfied
In file included from <source>:1:
/…/concepts:72:13: required for the satisfaction of
'convertible_to<_Tp, typename std::common_reference<_Tp1, _Tp2>::type>'
[with _Tp = const std::unique_ptr<int, std::default_delete<int> >&; _Tp2 = const std::nullptr_t&; _Tp1 = const std::unique_ptr<int, std::default_delete<int> >&]
/…/concepts:72:30: note: the expression 'is_convertible_v<_From, _To>
[with _From = const std::unique_ptr<int, std::default_delete<int> >&; _To = std::unique_ptr<int, std::default_delete<int> >]' evaluated to 'false'
72 | concept convertible_to = is_convertible_v<_From, _To>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

(为了易读性而编辑) Compiler Explorer link
std::common_reference_with<const std::unique_ptr<int>&, const std::nullptr_t&> 要求:
template < class T, class U >
concept common_reference_with =
std::same_as<std::common_reference_t<T, U>, std::common_reference_t<U, T>> &&
std::convertible_to<T, std::common_reference_t<T, U>> &&
std::convertible_to<U, std::common_reference_t<T, U>>;
std::common_reference_withstd::common_reference_t<const std::unique_ptr<int>&, const std::nullptr_t&>(见 compiler explorer link)。
把这些放在一起,有一个传递要求 std::unique_ptr<int> ,这相当于要求 std::convertible_to<const std::unique_ptr<int>&, std::unique_ptr<int>> 是可复制构造的。
为什么 std::unique_ptr<int> 不是引用?
为什么是 std::common_reference_t 而不是 std::common_reference_t<const std::unique_ptr<T>&, const std::nullptr_t&> = std::unique_ptr<T> ?两种类型的 const std::unique_ptr<T>& ( std::common_reference_t 是两个)的文档说:
  • If T1 and T2 are both reference types, and the simple common reference type S of T1 and T2 (as defined below) exists, then themember type type names S;
  • Otherwise, if std::basic_common_reference<std::remove_cvref_t<T1>, std::remove_cvref_t<T2>, T1Q, T2Q>::type exists, where TiQ is a unaryalias template such that TiQ<U> is U with the addition of Ti's cv- andreference qualifiers, then the member type type names that type;
  • Otherwise, if decltype(false? val<T1>() : val<T2>()), where val is a function template template<class T> T val();, is a valid type, thenthe member type type names that type;
  • Otherwise, if std::common_type_t<T1, T2> is a valid type, then the member type type names that type;
  • Otherwise, there is no member type.
sizeof...(T)const std::unique_ptr<T>& 没有简单的公共(public)引用类型,因为这些引用不能立即转换为公共(public)基类型(即 const std::nullptr_t& 格式错误)。 false ? crefUPtr : crefNullptrT 没有 std::basic_common_reference 特化。第三个选项也失败了,但我们触发了 std::unique_ptr<T>
对于 std::common_type_t<const std::unique_ptr<T>&, const std::nullptr_t&> std::common_type ,因为:

If applying std::decay to at least one of T1 and T2 produces adifferent type, the member type names the same type asstd::common_type<std::decay<T1>::type, std::decay<T2>::type>::type, ifit exists; if not, there is no member type.

std::common_type<const std::unique_ptr<T>&, const std::nullptr_t&> = std::common_type<std::unique_ptr<T>, std::nullptr_t> 确实存在;它是 std::common_type<std::unique_ptr<T>, std::nullptr_t> 。这就是引用被剥离的原因。

我们可以修改标准来支持这样的案例吗?
这已变成 P2404 ,它建议更改 std::unique_ptr<T>std::equality_comparable_withstd::totally_ordered_with 以支持仅移动类型。
为什么我们甚至有这些共同引用要求?
Does `equality_­comparable_with` need to require `common_reference`? 中, justification given by T.C.(最初来自 n3351 第 15-16 页)对于 std::three_way_comparable_with 的公共(public)引用要求是:

[W]hat does it even mean for two values of different types to be equal? The design says that cross-type equality is defined by mapping them to the common (reference) type (this conversion is required to preserve the value).


仅仅要求 equality_comparable_with 操作可能天真地期望这个概念是行不通的,因为:

[I]t allows having t == u and t2 == u but t != t2


因此,通用引用要求是为了数学上的健全性,同时允许以下可能的实现:
using common_ref_t = std::common_reference_t<const Lhs&, const Rhs&>;
common_ref_t lhs = lhs_;
common_ref_t rhs = rhs_;
return lhs == rhs;
使用 n3351 支持的 C++0X 概念,如果没有异构 == ,则此实现实际上将用作后备。
对于 C++20 的概念,我们需要一个异构的 operator==(T, U) 存在,所以永远不会使用这个实现。
注意,n3351 表示这种异构等式已经是等式的扩展,它只是在单一类型内进行了严格的数学定义。实际上,当我们编写异构相等操作时,我们假装这两种类型共享一个共同的父类(super class)型,而操作发生在该共同类型内部。
公共(public)引用要求能否支持这种情况?
也许 operator==(T, U) 的公共(public)引用要求太严格了。重要的是,数学要求只是存在一个共同的父类(super class)型,其中这个提升的 std::equality_comparable 是相等的,但是共同的引用要求要求更严格,另外要求:
  • 公共(public)父类(super class)型必须是通过 operator== 获得的父类(super class)型。
  • 我们必须能够形成对这两种类型的公共(public)父类(super class)型引用。

  • 放宽第一点基本上只是为 std::common_reference_t 提供一个明确的自定义点,您可以在其中明确选择一对类型来满足概念。对于第二点,在数学上,“引用”是没有意义的。因此,第二点也可以放宽,以允许公共(public)父类(super class)型可以从两种类型隐式转换。
    我们能否放宽公共(public)引用要求以更紧密地遵循预期的公共(public)父类(super class)型要求?
    这是很难做到的。重要的是,我们实际上只关心公共(public)父类(super class)型是否存在,但我们实际上从不需要在代码中使用它。因此,我们无需担心效率,甚至在编写通用父类(super class)型转换时是否无法实现。
    这可以通过更改 std::equality_comparable_withstd::common_reference_with 部分来实现:
    template <class T, class U>
    concept equality_comparable_with =
    __WeaklyEqualityComparableWith<T, U> &&
    std::equality_comparable<T> &&
    std::equality_comparable<U> &&
    std::equality_comparable<
    std::common_reference_t<
    const std::remove_reference_t<T>&,
    const std::remove_reference_t<U>&>> &&
    __CommonSupertypeWith<T, U>;

    template <class T, class U>
    concept __CommonSupertypeWith =
    std::same_as<
    std::common_reference_t<
    const std::remove_cvref_t<T>&,
    const std::remove_cvref_t<U>&>,
    std::common_reference_t<
    const std::remove_cvref_t<U>&,
    const std::remove_cvref_t<T>&>> &&
    (std::convertible_to<const std::remove_cvref_t<T>&,
    std::common_reference_t<
    const std::remove_cvref_t<T>&,
    const std::remove_cvref_t<U>&>> ||
    std::convertible_to<std::remove_cvref_t<T>&&,
    std::common_reference_t<
    const std::remove_cvref_t<T>&,
    const std::remove_cvref_t<U>&>>) &&
    (std::convertible_to<const std::remove_cvref_t<U>&,
    std::common_reference_t<
    const std::remove_cvref_t<T>&,
    const std::remove_cvref_t<U>&>> ||
    std::convertible_to<std::remove_cvref_t<U>&&,
    std::common_reference_t<
    const std::remove_cvref_t<T>&,
    const std::remove_cvref_t<U>&>>);
    特别地,该变化是变化到 equality_comparable_with这个假设 common_reference_with其中 __CommonSupertypeWith相差允许 __CommonSupertypeWith到通过尝试都 std::common_reference_t<T, U>T创建公共(public)引用产生的 UC(T&&)并且还引用汽提版本。更多详细信息,请参见 P2404

    在合并到标准之前,我如何解决 C(const T&)
    更改您使用的重载
    对于标准库中 std::equality_comparable_with(或任何其他 std::equality_comparable_with 概念)的所有使用,有一个谓词重载很有帮助,您可以将函数传递给它。这意味着您可以将 *_with 传递给谓词重载并获得所需的行为( 不是 std::equal_to() ,但不受约束15131313)。
    然而,这并不意味着不修复 std::ranges::equal_to 是个好主意。
    我可以扩展我自己的类型以满足 std::equal_to 吗?
    通用引用要求使用 std::equality_comparable_with ,其定制点为 std::equality_comparable_with ,目的是:

    The class template basic_common_reference is a customization point that allows users to influence the result of common_reference for user-defined types (typically proxy references).


    这是一个可怕的黑客,但如果我们编写一个支持我们想要比较的两种类型的代理引用,我们可以为我们的类型特化 std::common_reference_t ,使我们的类型能够满足 std::basic_common_reference 。另见 How can I tell the compiler that MyCustomType is equality_comparable_with SomeOtherType? 。如果您选择这样做,请注意; std::basic_common_reference 不仅被 std::equality_comparable_with 或其他 std::common_reference_t 概念使用,你还有可能在 future 导致级联问题。最好确保公共(public)引用实际上是公共(public)引用,例如:
    template <typename T>
    class custom_vector { ... };

    template <typename T>
    class custom_vector_ref { ... };
    std::equality_comparable_with 可能是 comparison_relation_withcustom_vector_ref<T> 之间,甚至可能在 custom_vector<T> 和 0x231414 之间的公共(public)引用的好选择。小心踩踏。
    如何扩展我无法控制的类型 custom_vector_ref<T>
    你不能。将 custom_vector<T> 专门用于您不拥有的类型( std::array<T, N> 类型或某些第三方库)在最好的情况下是不好的做法,在最坏的情况下是未定义的行为。最安全的选择是使用您拥有的可以比较的代理类型,或者编写您自己的 std::equality_comparable_with 扩展名,该扩展名具有用于自定义相等拼写的显式自定义点。

    好的,我知道这些要求的想法是数学上的稳健性,但是这些要求如何实现数学上的稳健性,为什么它如此重要?
    在数学上,相等是一种等价关系。然而,等价关系是在单个集合上定义的。那么我们如何定义两个集合 std::basic_common_referencestd:: 之间的等价关系?简单地说,我们改为定义 std::equality_comparable_with 上的等价关系。也就是说,我们取一个共同的父类(super class)型 AB,并在这个父类(super class)型上定义等价关系。
    这意味着我们的关系 C = A∪B必须定义无论身在何处 AB来的,所以我们必须有 c1 == c2c1c2(其中 a1 == a2a == bb1 == b2ai)。转换为 C++,这意味着 AbiBoperator==(A, A) 都必须是相同等式的一部分。
    这就是为什么 operator==(A, B)/ operator==(B, B) s 不满足 operator==(C, C) :而 iterator 可能实际上是某些等价关系的一部分,它既不是等价关系也不是等价关系的一部分,它既不是等价关系,也不是等价关系最后还是两个迭代器都不在最后?”)。
    实际上很容易写出一个实际上并不相等的 sentinel,因为你必须记住异构相等不是你正在写的单个 std::equality_comparable_with,而是四个不同的 operator==(iterator, sentinel) 必须全部一致。
    等一下,为什么我们需要所有四个 operator==(iterator, iterator) ;为什么我们不能只有 operator==operator==(A, B) 用于优化目的?
    这是一个有效的模型,我们可以这样做。然而,C++ 并不是柏拉图式的现实。尽管概念尽最大努力只接受真正满足语义要求的类型,但它实际上无法实现这一目标。因此,如果我们只检查 operator==operator== ,我们冒着 operator==(C, C)operator==(A, B) 做不同事情的风险。此外,如果我们可以有 operator==(A, B) ,那么这意味着根据我们在 operator==(C, C) 中的内容编写 operator==(A, A)operator==(B, B) 是微不足道的。也就是说,要求 operator==(C, C)operator==(A, A) 的危害是相当低的,作为返回,我们得到了更高的信心,即我们实际上是相等的。
    然而,在某些情况下,这会遇到粗糙的边缘;见 P2405
    好累啊我们不能只要求 operator==(B, B) 是实际相等吗?无论如何,我永远不会实际使用 operator==(C, C)operator==(A, A);我只关心能够进行交叉类型比较。
    实际上,我们需要 operator==(B, B) 是实际相等的模型可能会起作用。在这个模型下,我们会有 operator==(A, B) ,但这在所有已知上下文中的确切含义可以敲定。然而,这不是标准的方向是有原因的,在了解是否或如何改变它之前,他们必须首先了解为什么选择标准的模型。

    关于c++ - 为什么不的unique_ptr在C++ 20 equality_comparable_with nullptr_t?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66937947/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com