为啥 std::compare_three_way 不是模板结构/函子

Posted

技术标签:

【中文标题】为啥 std::compare_three_way 不是模板结构/函子【英文标题】:Why std::compare_three_way is not a template struct/functor为什么 std::compare_three_way 不是模板结构/函子 【发布时间】:2020-10-29 01:54:37 【问题描述】:

例如std::less的比较被定义为模板结构

template< class T = void >
struct less;

虽然std::compare_three_way 被定义为一个普通的结构,它的operator() 是一个模板函数。 (来自 MSVC 的代码)

struct compare_three_way 
    template <class _Ty1, class _Ty2>
        requires three_way_comparable_with<_Ty1, _Ty2> // TRANSITION, GH-489
    constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const
        noexcept(noexcept(_STD forward<_Ty1>(_Left) <=> _STD forward<_Ty2>(_Right))) /* strengthened */ 
        return _STD forward<_Ty1>(_Left) <=> _STD forward<_Ty2>(_Right);
    

    using is_transparent = int;
;

那么为什么std::compare_three_way 不是模板结构呢?

template <class _Ty1, class _Ty2>
    requires three_way_comparable_with<_Ty1, _Ty2>
struct compare_three_way 
    constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const; 
;

顺便说一句,我可以在我自己的容器实现中将std::less&lt;T&gt; 替换为std::three_way_compare,比如C# 中的Comparer&lt;T&gt;

【问题讨论】:

您希望它成为类模板而不是使运算符模板化有什么特别的原因吗?还是您在问为什么不一致? @chris 在模板中使用,如std::less&lt;T&gt; in std::set&lt;T&gt; 我认为 std::less 是历史性的,这就是为什么自 c++14 以来,有一个 专业化,这意味着你真的不需要提供 T , T 可以从参数推导中推导出来。 std::less&lt;T&gt; 为您提供了一种能够比较 Ts 的类型。 std::compare_three_way 为您提供了一种能够比较 Ts 的类型。 (std::less&lt;&gt; 还为您提供了一种能够比较 Ts 的类型。)容器需要能够比较 Ts 的东西。这里有一个错误的假设。 【参考方案1】:

std::less(及其好友)的比较定义如下:

bool operator()( const T& lhs, const T& rhs ) const;

由于此函数调用运算符不是模板,它只能比较用于实例化std::less 模板的类型的对象。

在后来的语言修订版中,这些比较器通过专门化 std::less&lt;&gt; 进行了扩展,以支持通过对函数调用运算符本身进行模板化来比较不同类型的对象:

template< class T, class U>
constexpr auto operator()( T&& lhs, U&& rhs ) const
  -> decltype(std::forward<T>(lhs) < std::forward<U>(rhs));
    

在大多数用例中,这在很大程度上淘汰了同类版本 (std::less&lt;T&gt;),因为它要么是等效的,要么由于不强制转换为通用类型而更有效。保留旧的同构比较器以实现向后兼容性。

在提出std::compare_three_way的时候,异构查找已经存在,所以从未引入同构版本。

在模板中使用它,比如std::set&lt;T&gt;中的std::less&lt;T&gt;

您可以在std::set&lt;T&gt;std::set&lt;U&gt; 等中使用std::compare_three_way(象征性地),就像您可以使用std::less&lt;&gt; 一样。你(可能)不需要std::less&lt;T&gt; 也不需要std::less&lt;U&gt; - 也不需要std::compare_three_way&lt;T&gt;,它们都不存在。

【讨论】:

std::ranges::less 也不是类模板,尽管它最初是:P1291R0。【参考方案2】:

由于看起来std::compare_three_way 执行转发,当模板参数用于struct 时,它无法工作。 C++ Standard section 13.10.3.2 §3:

转发引用是对 cv 非限定模板参数的右值引用不代表类模板的模板参数(在类模板参数推导期间([over.match.class.deduct ]))。

因此,这里的_Ty1&amp;&amp;_Ty2&amp;&amp; 只是右值引用,无法正确转发:

template <class _Ty1, class _Ty2>
    requires three_way_comparable_with<_Ty1, _Ty2>
struct compare_three_way 
    constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const; 
;

上面声明的模板结构只接受右值表达式作为其操作数,这不会太有用。让这个struct 可用的唯一方法是使参数const 引用。 MSVC 实现者选择了转发方式。

【讨论】:

以上是关于为啥 std::compare_three_way 不是模板结构/函子的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 glTranslatef?为啥不直接更改渲染坐标?

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?