什么是透明比较器?


106

在C ++ 14中,关联容器似乎已从C ++ 11进行了更改– [associative.reqmts] / 13说:

成员函数模板findcountlower_boundupper_bound,并且equal_range不得,除非类型参与重载决议Compare::is_transparent存在。

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

C ++ 14还提供了如下库模板:

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>, ...,所以没有默认设置失去了findcount等会员?


例如,请参阅此cppreference描述。我现在感觉很愚蠢,因为我注意到“成员函数模板 ”一词...
Kerrek SB 2013年


Answers:


60

这解决了什么问题,

参见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已添加到所有菱形运算符中,并用作标记类型以指示应在关联容器中启用新功能。从技术上讲,容器不需要“透明函子”,只需一个支持使用异构类型调用的容器即可(例如,根据STL的定义,https://stackoverflow.com/a/18940595/981959中的pointer_comp类型不是透明的,pointer_comp::is_transparent允许将其用于解决问题)。如果你永远只能在你的查找std::set<T, C>与类型的键TintC仅需要调用同类型的参数Tint(以任何顺序),它并不需要真正的透明。我们之所以使用该名称,部分原因是我们无法提出一个更好的名称(我更愿意,is_polymorphic因为此类函子使用静态多态性,但是已经有一个std::is_polymorphic类型特征引用动态多态性)。


3
嘿,您是STL对他说的那个人吗,“当然,您可以在与羊毛明星联系的话题中进行模板参数推导”?
Kerrek SB 2013年

10
不,我当时不在那儿,但是有些人头脑中的编译器比我多得多:)
Jonathan Wakely 2013年

我猜“钻石算子” <>在链接的提案中引用,但是该提案未引入<>-它是用于空模板参数列表的现有语法。“钻石算子函子”将稍微减少混乱。
Qwertie

33

在C ++ 11有没有成员模板find()lower_bound()等等。也就是说,没有被这种变化丢失。n3657引入了成员模板,以允许将异类密钥与关联容器一起使用。除了好与坏的示例外,我看不到任何其他有用的具体示例!

is_transparent用途旨在避免不必要的转换。如果成员模板不受约束,则现有代码可能会直接传递原本无需成员模板即可进行转换的对象。来自n3657的示例用例是在std::set<std::string>使用字符串文字查找a中的对象:使用C ++ 11定义,std::string当将字符串文字传递给相应的成员函数时,将构造一个对象。通过更改,可以直接使用字符串文字。如果底层比较功能对象仅在实现方面std::string是不好的,因为现在std::string将为每个比较创建一个。另一方面,如果基础比较功能对象可以采用std::string 和字符串文字,可以避免构造临时对象。

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


谢谢-请参阅我对另一个问题的评论:默认情况下,您是否获得透明行为?
Kerrek SB 2013年

8
@KerrekSB:当is_transparent根据23.2.4 [associative.reqmts]段落13在比较函数对象中定义透明行为时,将启用透明行为。默认比较函数对象是std::less<Key>根据23.4.2 [associative.map.syn]和23.4定义的。 3 [associative.set.syn]。根据20.10.5 [比较]段落4的一般性模板std::less<...>限定嵌套类型is_transparentstd::less<void>专业化一样。也就是说,不,默认情况下您没有透明的运算符。
DietmarKühl2013年

您对命名有任何想法吗?我的意思是为什么is_transparent
–plasmcel

您想要一个“有用的具体示例”吗?这是我的用例
spraff

19

以下是n3657的所有copy-pasta 。

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

答:关联容器查找功能(find,lower_bound,upper_bound,equal_range)仅采用key_type的参数,要求用户构造(隐式或显式)key_type的对象以进行查找。这可能是昂贵的,例如,当比较器功能仅查看对象的一个​​字段时,构造一个大对象以在一组中搜索。用户强烈希望能够使用与key_type相当的其他类型进行搜索。

问:这可以解决什么问题

答: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()函数还将防止在指针容器中查找NULL,这将导致以前有效的代码不再编译,但是,与静默性能回归相比,这被认为是不那么严重的问题。

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

答:该提案通过使用成员函数模板重载查找成员函数来修改其中的关联容器。没有语言更改。

问:默认集合也会丢失其查找,计数等成员吗?

答:几乎所有现有的C ++ 11代码都不会受到影响,因为除非使用新的C ++ 14库功能作为比较功能,否则不存在成员函数。

引用Yakk

在C ++ 14中,如果Compare :: is_transparent存在,则std :: set :: find是模板函数。您传入的类型不必是Key,只需在比较器下相等即可。

和n3657,

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

n3421提供了“透明算子子”的示例。

完整的代码是在这里


1
是否std::set<std::string>真正从“传递受益于char const *通过”,或者你需要做std::set<std::string, std::less<>>
Kerrek SB 2013年

@Kerrek我认为“传递char const *”是他们试图避免的问题,如果我没有记错的话。看一下措辞:With the change proposed by N3465 the std::set::find() function would be an unconstrained template which would pass the const char* through to the comparator function, std::less<std::string>, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue.

您对第13段的引用与我的说法相反:“除非类型存在/不存在” ...?!
Kerrek SB 2013年

4
@KerrekSB,这是我的错,N3657应该说“存在”,但我写了“不存在”……这是在最后一分钟写的一篇晚论文。标准草案是正确的。
乔纳森·威克里

3
是的,引用我的意思而不是当时的意思可能更清楚:)
Jonathan Wakely 2013年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.