什么是透明比较器?

What are transparent comparators?

提问人:Kerrek SB 提问时间:12/2/2013 最后编辑:Alexis WilkeKerrek SB 更新时间:11/19/2022 访问量:36186

问:

在 C++14 中,关联容器似乎从 C++11 发生了变化 – [associative.reqmts]/13 说:

除非类型存在,否则成员函数模板 、 、 和 不应参与重载解析。findcountlower_boundupper_boundequal_rangeCompare::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::setKey, Compare = std::less<Key>, ...findcount

C ++14 C ++-FAQ

评论

0赞 Kerrek SB 12/2/2013
例如,请参阅此 cppreference 说明。我现在感觉很愚蠢,因为我注意到“成员函数模板”这个词......
5赞 12/2/2013
可能与此相关:stackoverflow.com/questions/18939882/...
0赞 Cubbi 12/2/2013
cppreference 也有关于 en.cppreference.com/w/cpp/utility/functional/less_void 的简介

答:

37赞 Dietmar Kühl 12/2/2013 #1

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

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

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

评论

0赞 Kerrek SB 12/2/2013
谢谢 - 请参阅我对另一个问题的评论:默认情况下您是否获得透明行为?
8赞 Dietmar Kühl 12/2/2013
@KerrekSB:当根据 23.2.4 [associative.reqmts] 第 13 段在比较函数对象中定义时,将启用透明行为。默认的比较函数对象是 23.4.2 [associative.map.syn] 和 23.4.3 [associative.set.syn]。根据 20.10.5 [比较] 第 4 段,通用模板没有定义嵌套类型,但专用化定义了嵌套类型。也就是说,不,默认情况下您没有透明运算符。is_transparentstd::less<Key>std::less<...>is_transparentstd::less<void>
0赞 plasmacel 6/20/2018
你对命名有什么想法吗?我的意思是为什么?is_transparent
0赞 spraff 1/5/2019
你想要一个“有用的具体例子”吗?这是我的用例
23赞 user1508519 12/2/2013 #2

以下是 n3657 的所有复制粘贴。

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

一个。关联容器查找函数(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的 认为此性能问题是一个严重的问题。这 template find() 函数还可以防止在 指针容器,这会导致以前有效的代码不再有效 编译,但这被视为一个不如沉默那么严重的问题 性能回归

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

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

问:默认集也会丢失其 find、count 等成员

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

引用 Yakk 的话,

在 C++14 中,std::set::find 是一个模板函数,如果 Compare::is_transparent 存在。您传入的类型不需要 是 Key,等同于你的比较器。

和 n3657,

在23.2.4 [associative.reqmts]中增加第13段: 成员函数模板查找、lower_bound、upper_bound和 equal_range不得参与过载解决,除非 类型 Compare::is_transparent 不存在 确实存在。

n3421 提供了“透明操作员函子”的示例。

完整代码在这里

评论

1赞 Kerrek SB 12/2/2013
是否真的从“通过”中受益,或者你需要做一个?std::set<std::string>char const *std::set<std::string, std::less<>>
0赞 12/2/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.
0赞 Kerrek SB 12/2/2013
你和我第 13 段的引文说的相反:“除非类型存在/不存在”......?!
5赞 Jonathan Wakely 12/5/2013
@KerrekSB,那是我的错,N3657 应该说“存在”,但我写的是“不存在”......这是在最后一刻写的一篇迟到的论文。标准草案是正确的。
4赞 Jonathan Wakely 12/5/2013
是的,引用我想说的话而不是我当时实际说的话可能会更清楚:)
81赞 Jonathan Wakely 12/5/2013 #3

这解决了什么问题,

参见 Dietmar 的回答和 remyabel 的回答

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

不,不是默认的。

etc. 的新成员函数模板重载允许您使用与容器的键相当的类型,而不是使用键类型本身。请参阅 Joaquín Mª López Muñoz 的 N3465,了解添加此功能的基本原理和详细、仔细编写的提案。find

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

N3657 提案(这是我和 STL 基于 N3465 和 Joaquín 后来未发表的草案的最后一刻修订版)的主要区别在于将类型添加为可用于选择加入新功能的协议。is_transparent

如果不使用“透明函子”(即定义类型的函子),则容器的行为与它们一直以来的行为相同,这仍然是默认设置。is_transparent

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

使用别名模板很容易:std::less<>

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

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

评论

4赞 Kerrek SB 12/5/2013
嘿,你是STL在演讲中说的那个人,“当然你可以在脑海中做模板论证推论”吗?
14赞 Jonathan Wakely 12/5/2013
不,我不在那里,但有些人的脑海中有比我多得多的顺从编译器:)
1赞 Qwertie 1/23/2019
我猜链接的提案中提到了“菱形运算符”,但该提案没有引入 - 它是空模板参数列表的现有语法。“钻石运算符函子”会不那么令人困惑。<><>
0赞 Martin Ba 6/7/2022
@JonathanWakely - 您知道为什么 std::string 不指定异构比较器吗?
0赞 Qwert Yuiop 12/21/2022
这适用于 std::unordered_set 吗?
7赞 woolstar 12/5/2013 #4

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

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