【问题标题】:Why does std::inlcudes use the less than operator in the condition rather than the equality operator?为什么 std::includes 在条件中使用小于运算符而不是相等运算符?
【发布时间】:2021-11-16 19:13:52
【问题描述】:

我有来自https://en.cppreference.com/w/cpp/algorithm/includes的算法std::includes的这个实现

template<class InputIt1, class InputIt2>
bool includes(InputIt1 first1, InputIt1 last1,
              InputIt2 first2, InputIt2 last2)
{
    for (; first2 != last2; ++first1)
    {
        if (first1 == last1 || *first2 < *first1)
            return false;
        if ( !(*first1 < *first2) )
            ++first2;
    }
    return true;
}
  • 它工作得很好但是我想知道为什么在循环内的第二个if statement 中使用小于运算符&lt; 而不是相等运算符==

  • 这是我的类似实现:

    template<class InputIt1, class InputIt2>
    bool including(InputIt1 first1, InputIt1 last1,
                InputIt2 first2, InputIt2 last2){
    
       while (first2 != last2){
          if (first1 == last1 || *first2 < *first1)
             return false;
          if ( *first1 == *first2 )
             ++first2;
          ++first1;
       }
        return true;
    }
    
    
    int main(){
       std::vector<int> v{1, 5, 7, 23, 24, 57, 77};
      //  std::sort(v.begin(), v.end());
        int a[]{7, 24};
        std::cout << including(v.begin(), v.end(), a, a + 2) << '\n'; // 1
        std::cout << std::includes(v.begin(), v.end(), a, a + 2) << '\n'; // 1
    
    }
    
  • 所以我得出结论:

  • 第一个条件:*first2 &lt; *first1 -> 返回 false。

  • 第二个条件:!(*first1 &lt; *firs2) -> *first1 &gt;= *first2

所以*first1 &gt; *first2 导致算法返回false*first1 &gt;= *first2 导致firsts2 递增,所以first2 递增的唯一条件是*first1 == *first2 那么为什么使用小于&lt; 以及否定运算符! 运算符而不是像我的实现中那样直接使用等于运算符==

  • 这仅仅是为了某种可读性和兼容性吗?
  • 我的分析正确吗?谢谢!

【问题讨论】:

  • 因为大多数标准算法和标准包含使用小于比较进行比较(和排序)。因此,最小意外原则适用。正如您在问题中所指出的,相等可以从小于合成。另见C++ named requirements: Compare

标签: c++ algorithm


【解决方案1】:

算法在排序范围内工作。您需要&lt; 关系来对一系列元素进行排序,但不一定需要== 关系,因此使用&lt; 的限制更少,更通用。


还要考虑大多数算法和容器使用&lt; 而不是== 来比较元素。参见例如std::map:

在标准库使用比较要求的任何地方,唯一性都是通过使用等价关系来确定的。用不精确的术语来说,如果两个对象 a 和 b 的比较值都不小于另一个,则认为这两个对象是等价的(不唯一的):!comp(a, b) && !comp(b, a)。

!comp(a,b) &amp;&amp; !comp(b,a) 时,映射中的两个键被视为等效(不相等!)。大多数情况下,这与a == b 相同,但不一定。

【讨论】:

  • 我不认为“大多数算法和容器”的论点很强,但是被排序的前提条件并因此暗示存在小于关系是一个好的前提。
  • @eerorika 实际上它可能是一个反论点,因为我希望includes 检查相等而不是“只是”等价。不知道我是否应该删除那段....
【解决方案2】:

所有与元素排序相关的标准算法都只使用&lt; 运算符,而从不使用&lt;=&gt;&gt;===!= 运算符。

这意味着一个应该支持排序的类类型只需要定义operator&lt;,其他的可能不需要。

但另一个原因是大多数排序/排序算法并不假定对象是“完全排序的”,这意味着 a&lt;ba==ba&gt;b 中的一个总是正确的。相反,他们假设它们具有严格的弱排序,其中具有!(a&lt;b) &amp;&amp; !(b&lt;a) true 的元素对形成等价类,但不一定相等。

例如,一个不区分大小写的 ASCII 字符串的简单类:

#include <string>
#include <strings.h>
class ci_string {
public:
    explicit ci_string(const char* s) : m_str(s) {}
    const char* c_str() const { return m_str.c_str(); }

    friend bool operator<(const ci_string& s1, const ci_string& s2)
    { return strcasecmp(s1.c_str(), s2.c_str()) < 0; }
    friend bool operator>(const ci_string& s1, const ci_string& s2)
    { return s2 < s1; }
    friend bool operator==(const ci_string& s1, const ci_string& s2)
    { return s1.m_str == s2.m_str; }
    friend bool operator!=(const ci_string& s1, const ci_string& s2)
    { return !(s1 == s2); }
private:
    std::string m_str;
};

(不适用于多字节编码或非 ASCII 字母。)

现在ci_string("HELLO")ci_string("HeLLo")ci_string("hello") 都等价但不等价。对于像这样的代码:

std::vector<ci_string> v1{
    ci_string("alpha"), ci_string("beta"), ci_string("gamma"),
    ci_string("delta"), ci_string("epsilon")};
std::vector<ci_string> v2{ci_string("BETA"), ci_string("Delta")};
bool c = std::includes(v1.begin(), v1.end(), v2.begin(), v2.end());

仅使用&lt; 的实现将返回true,但使用== 的实现可能会返回false。 (这个结果对于“包含”是否真的有意义是一个有趣的问题,但这就是它的定义方式。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-05
    • 1970-01-01
    • 2019-07-10
    • 2012-08-10
    • 1970-01-01
    • 2022-08-17
    • 1970-01-01
    相关资源
    最近更新 更多