对标准::binary_search的神秘限制
Mystical restriction on std::binary_search
>问题描述:
考虑某个具有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 ()
添加到返回其name
的Human
类中,然后使用比较进行两个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
传递给比较函数(或<
运算符(的值类型必须相同。因此,从形式上讲,我认为使用两种不同类型值的比较器是完全可以的。
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- 如何从Windows应用程序输出到标准?
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 标准::变体的赋值运算符
- 捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件
- 如何在 Mac 上使用 c++17 并行标准库算法?
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 并行标准::复制复杂性