什么是透明比较器?

时间:2013-12-01 21:21:58

标签: c++ c++14 c++-faq

在C ++ 14中,关联容器似乎已经从C ++ 11改变了 - [associative.reqmts] / 13说:

  

除非{{1}类型,否则成员函数模板findcountlower_boundupper_boundequal_range不得参与重载解析存在。

使比较器“透明”的目的是什么?

C ++ 14还提供了这样的库模板:

Compare::is_transparent

例如,template <class T = void> struct less { constexpr bool operator()(const T& x, const T& y) const; typedef T first_argument_type; typedef T second_argument_type; typedef bool result_type; }; template <> struct less<void> { template <class T, class U> auto operator()(T&& t, U&& u) const -> decltype(std::forward<T>(t) < std::forward<U>(u)); typedef *unspecified* is_transparent; }; 会有一个透明的比较器,但std::set<T, std::less<T>> 会有一个。

这解决了什么问题,这会改变标准容器的工作方式吗?例如,std::set<T, std::less<>>的模板参数仍为std::set,默认设置也会丢失其Key, Compare = std::less<Key>, ...find等成员吗?

4 个答案:

答案 0 :(得分:51)

  

这解决了什么问题,

见Dietmar和remyabel的回答。

  

这会改变标准容器的工作方式吗?

不,不是默认。

find等新成员函数模板重载允许您使用与容器键相当的类型,而不是使用键类型本身。请参阅JoaquínMªLópezMuñoz撰写的N3465以及添加此功能的详细,精心编写的提案。

在布里斯托尔会议上,LWG同意异端查找功能是有用且可取的,但我们无法确定Joaquín的提议在所有情况下都是安全的。 N3465提案会对某些程序造成严重问题(请参阅对现有代码的影响部分)。 Joaquín准备了一份更新的草案提案,其中包含一些具有不同权衡的替代实施方案,这对于帮助LWG了解优缺点非常有用,但它们都有可能以某种方式破坏某些程序,因此没有就增加该功能达成共识。我们认为尽管无条件地添加功能是不安全的,但如果默认情况下禁用它并且只是“选择加入”,那将是安全的。

N3657提案(由我自己和STL根据N3465进行的最后一分钟修订以及后来Joaquín未发布的草案)的主要区别是添加is_transparent键入可用于选择加入新功能的协议。

如果你不使用“透明函子”(即定义is_transparent类型的那个),那么容器的行为与它们一直以来的行为相同,而且仍然是默认值。

如果您选择使用std::less<>(这是C ++ 14的新功能)或其他“透明仿函数”类型,那么您将获得新功能。

使用别名模板轻松使用std::less<>

template<typename T, typename Cmp = std::less<>, typename Alloc = std::allocator<T>>
  using set = std::set<T, Cmp, Alloc>;

名称is_transparent来自STL的N3421,它将“钻石运算符”添加到C ++ 14中。 “透明函子”是接受任何参数类型(不必相同)并简单地将这些参数转发给另一个运算符的函数。这样的仿函数恰好是您想要在关联容器中进行异构查找的内容,因此类型is_transparent被添加到所有菱形运算符中,并用作标记类型以指示应在关联容器中启用新功能。从技术上讲,容器不需要“透明函子”,只需支持使用异构类型调用它(例如https://stackoverflow.com/a/18940595/981959中的pointer_comp类型根据STL的定义不透明,但定义{{ 1}}允许它用于解决问题)。如果您只使用类型为pointer_comp::is_transparentstd::set<T, C>的密钥在T中查找,则int只需要使用C和{{类型的参数进行调用1}}(以任何顺序),它不需要真正透明。我们使用该名称的部分原因是因为我们无法提出更好的名称(我更喜欢T因为这些仿函数使用静态多态,但是已经有int类型特征引用了动态多态性) 。

答案 1 :(得分:30)

在C ++ 11中,没有成员模板find()lower_bound()等等。也就是说,此更改不会丢失任何内容。使用n3657引入了成员模板,以允许异构密钥与关联容器一起使用。除了好的和坏的例子之外,我没有看到任何有用的具体例子!

is_transparent用途旨在避免不必要的转化。如果成员模板不受约束,则现有代码可以直接传递直接对象,这些对象在没有成员模板的情况下进行转换。来自n3657的示例用例是使用字符串文字在std::set<std::string>中定位对象:使用C ++ 11定义,在将字符串文字传递给相应的成员函数时构造std::string对象。通过更改,可以直接使用字符串文字。如果底层比较函数对象仅以std::string的方式实现,那么现在将为每次比较创建std::string。另一方面,如果底层比较函数对象可以采用std::string和字符串文字,则可以避免构造临时对象。

比较函数对象中的嵌套is_transparent类型提供了一种指定是否应使用模板化成员函数的方法:如果比较函数对象可以处理异构参数,则它定义此类型以指示它可以有效地处理不同的论点。例如,新的运算符函数对象只委托给operator<()并声称是透明的。这至少适用于std::string,它比以char const*为参数的运算符重载更少。由于这些函数对象也是新的,即使它们做错了(即需要某种类型的转换),它至少也不会导致性能下降的静默变化。

答案 2 :(得分:18)

以下是来自n3657的所有copy-pasta。

  

Q值。 使比较器“透明”的目的是什么?

     

一个。关联容器查找函数(find,lower_bound,   upper_bound,equal_range)只接受key_type的参数,需要   用户构造(隐式或显式)的对象   key_type进行查找。这可能很昂贵,例如建造一个   仅当比较器功能时,在一个集合中搜索的大对象   看着对象的一个​​字段。用户强烈渴望   能够使用与其相似的其他类型进行搜索   为key_type。

     

Q值。 这解决了什么问题

     

一个。 LWG担心代码如下:

std::set<std::string> s = /* ... */;
s.find("key");
     

在C ++ 11中,这将构造一个单独的std :: string临时   将其与元素进行比较以找到密钥。

     

通过N3465提出的更改,std :: set :: find()函数会   是一个无约束的模板,它将传递const char *   到比较器函数,std :: less,哪个会   为每次比较构造一个std :: string临时文件。 LWG   认为这个性能问题是一个严重的问题。该   模板find()函数也会阻止在a中查找NULL   指针容器,不再导致以前有效的代码   编译,但这被认为是一个比沉默更严重的问题   绩效回归

     

Q值。 这会改变标准容器的工作方式

     

一个。该提议修改了和中的关联容器   通过使用成员函数重载查找成员函数   模板。没有语言变化。

     

Q值。 默认设置也会丢失其查找,计数等成员

     

一个。几乎所有现有的C ++ 11代码都不受影响,因为该成员   除非使用新的C ++ 14库特性,否则不存在函数   作为比较函数。

引用Yakk

  

在C ++ 14中,std :: set :: find是一个模板函数if   比较:: is_transparent存在。您传入的类型不需要   是关键,在你的比较器下等同。

和n3657,

  

在23.2.4 [associative.reqmts]中添加第13段:   成员函数模板find,lower_bound,upper_bound和   equal_range不应参与重载决策,除非   类型Compare :: is_transparent 不存在确实存在。

n3421提供了"Transparent Operator Functors"的示例。

full code is here

答案 3 :(得分:6)

Stephan T Lavavej谈论编译器不断创建临时代码的问题,以及他的透明运算符函数的提议将如何在c ++ 1y中解决这个问题

GoingNative 2013 - Dont help the Compiler(大约小时标记)