对标准::binary_search的神秘限制

Mystical restriction on std::binary_search

本文关键字:search 标准 binary      更新时间:2023-10-16

>问题描述:
考虑某个具有std::string name成员的结构。为了清楚起见,我们假设它是一个struct Human,表示有关人的信息。除了name它还可以有许多其他数据成员。
设一个容器std::vector<Human> vec,其中对象已经按name排序。同样为了清楚起见,假设所有名称都是唯一的。
问题是:有一些字符串nameToFind找出数组中是否存在具有这种名称的元素。

解决方案和我的进展:
显而易见的自然解决方案似乎使用 std::binary_search 函数执行二叉搜索。但是有一个问题:被搜索的元素的类型(std::string(与容器中元素的类型(Human(不同,std::binary_search需要一个规则来比较这些元素。我试图通过三种方式解决这个问题,如下所述。提供前两个只是为了说明我的解决方案的演变和我遇到的问题。我的主要问题涉及第三个问题。

尝试 1:将std::string转换为Human

编写一个比较函数:

bool compareHumansByNames( const Human& lhs, const Human& rhs )
{
   return lhs.name < rhs.name;
}

然后添加一个构造函数,该构造函数从std::string构造一个Human对象:

struct Human
{
   Human( const std::string& s );
   //... other methods
   std::string name;
   //... other members
};

并按以下形式使用binary_search:

std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames );

似乎有效,但出现了两个大问题:
首先,如何初始化其他数据成员但Human::name,特别是在它们没有默认构造函数的情况下?设置魔术值可能会导致创建语义上非法的对象。
其次,我们必须将此构造函数声明为非explicit,以允许在算法期间进行隐式转换。这样做的不良后果是众所周知的。
此外,这样的临时Human对象将在每次迭代时构建,这可能会变得非常昂贵。

尝试 2:将Human转换为std::string

我们可以尝试将一个operator string ()添加到返回其nameHuman类中,然后使用比较进行两个std::string。但是,由于以下原因,这种方法也很不方便:

首先,由于这里讨论的问题,代码不会立即编译。我们将不得不做更多的工作才能使编译器使用适当的operator <
其次,"将人转换为字符串"是什么意思?这种转换的存在会导致类Human在语义上错误的使用,这是不希望的。

尝试 3:在没有转化的情况下进行比较。

到目前为止,我得到的最佳解决方案是创建一个

struct Comparator
{
   bool operator() ( const Human& lhs, const std::string& rhs )
   {
      return lhs.name < rhs;
   }
   bool operator() ( const std::string& lhs, const Human& rhs )
   {
      return lhs < rhs.name;
   }
};

并使用二叉搜索作为

binary_search( vec.begin(), vec.end(), nameToFind, Comparator() );

这编译和执行正确,一切似乎都很好,但这是有趣的部分开始的地方:

看看 http://www.sgi.com/tech/stl/binary_search.html。这里说">ForwardIterator的值类型与T的类型相同"。相当令人困惑的限制,我上一个解决方案打破了它。让我们看看C++标准对此有何规定:


25.3.3.4 binary_search

template<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value);
template<class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp);

要求:类型 T 小于可比性 (20.1.2(。


没有明确说明ForwardIterator的类型。但是,在 20.1.2 中给出的 LessThanComparable 的定义中,它说是关于相同类型的两个元素的比较。这是我不明白的。这是否确实意味着要搜索的对象类型和容器对象的类型必须相同,并且我的解决方案打破了此限制?或者它不是指使用comp比较器的情况,而只是关于使用默认operator <进行比较的情况?在第一种情况下,我对如何使用std::binary_search来解决这个问题而没有遇到上述问题感到困惑。

提前感谢您的帮助并抽出时间阅读我的问题。

注意:我知道手动编写二进制搜索不需要时间,并且可以立即解决问题,但为了避免重新发明轮子,我想使用 std::binary_search。此外,根据标准找出这种限制的存在对我来说也非常有趣。

如果您的目标是查找是否存在具有给定名称的Human,那么以下内容应该肯定有效:

const std::string& get_name(const Human& h)
{
    return h.name;
}
...
bool result = std::binary_search(
    boost::make_transform_iterator(v.begin(), &get_name),
    boost::make_transform_iterator(v.end(), &get_name),
    name_to_check_against);

[完全重写;忽略注释]

措辞已从 C++03 更改为 C++0x。在后者中,不再要求T低于可比性,大概是为了减轻这种不必要的限制。

新标准只要求comp(e, value)意味着!comp(value, e)。因此,只要您的比较器实现了两个方向,您应该能够合法地搜索一个string作为值,使用实现两个非对称比较的比较器函子(即您的"尝试 3"(。

我认为这里的标准是表达式fucntor(a, b)需要是一个有效的严格弱排序,无论算法是否决定做类似functor(*begin, *(begin + 1))的事情。因此,我认为您的比较器需要提供超载operator()(Human, Human)才能符合要求。

也就是说,我认为这是标准没有明确允许的事情之一,但很少或没有利用标准提供的纬度的实现。

我不认为标准中的任何地方都要求binary_search传递给比较函数(或<运算符(的值类型必须相同。因此,从形式上讲,我认为使用两种不同类型值的比较器是完全可以的。