【问题标题】:What are transparent comparators?什么是透明比较器?
【发布时间】:2013-12-17 12:26:54
【问题描述】:

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

成员函数模板findcountlower_boundupper_boundequal_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&lt;T, std::less&lt;T&gt;&gt;没有有一个透明的比较器,但std::set&lt;T, std::less&lt;&gt;&gt;有一个。

这解决了什么问题,这是否改变了标准容器的工作方式?比如std::set的模板参数还是Key, Compare = std::less&lt;Key&gt;, ...,那么默认集合是否就失去了findcount等成员?

【问题讨论】:

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


【解决方案1】:

这解决了什么问题,

请参阅 Dietmar's answerremyabel's answer

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

不,默认情况下不是。

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

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

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

如果您不使用“透明函子”(即定义 is_transparent 类型的函子),则容器的行为与以往相同,这仍然是默认值。

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

通过别名模板轻松使用std::less&lt;&gt;

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 的定义不是透明的,但定义 pointer_comp::is_transparent 允许它用来解决问题)。如果您只使用Tint 类型的键在std::set&lt;T, C&gt; 中查找,那么C 只需要使用Tint 类型的参数(按任意顺序)调用,它不会不需要真正透明。我们使用这个名字的部分原因是我们想不出更好的名字(我更喜欢is_polymorphic,因为这样的函子使用静态多态性,但已经有一个std::is_polymorphic 类型特征指的是动态多态性)。

【讨论】:

  • 嘿,你是不是 STL 在与羊毛之星链接的谈话中说“你当然可以在头脑中进行模板参数推导”的那个人?
  • 不,我不在场,但有些人的编译器比我的要多得多:)
  • 我猜“菱形运算符”是指链接提案中的&lt;&gt;,但该提案没有引入&lt;&gt; - 它是空模板参数列表的现有语法。 “Diamond operator functors”不会那么混乱。
【解决方案2】:

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

is_transparent 的使用旨在避免不必要的转换。如果成员模板不受约束,则现有代码可能会直接通过在没有成员模板的情况下会被转换的对象。来自 n3657 的示例用例是使用字符串文字在 std::set&lt;std::string&gt; 中定位对象:使用 C++11 定义,在将字符串文字传递给相应的成员函数时构造 std::string 对象。通过更改,可以直接使用字符串文字。如果底层比较函数对象是专门根据std::string 实现的,那很糟糕,因为现在将为每个比较创建一个std::string。另一方面,如果底层比较函数对象可以采用std::string 和字符串字面量,则可以避免构建临时对象。

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

【讨论】:

  • 谢谢 - 请参阅我对另一个问题的评论:默认情况下您是否获得透明行为?
  • @KerrekSB:根据 23.2.4 [associative.reqmts] 第 13 段在比较函数对象中定义 is_transparent 时启用透明行为。默认比较函数对象为 std::less&lt;Key&gt; 根据到 23.4.2 [associative.map.syn] 和 23.4.3 [associative.set.syn]。根据 20.10.5 [比较] 第 4 段,std::less&lt;...&gt; 的通用模板 not 定义了嵌套类型 is_transparent,但 std::less&lt;void&gt; 特化却定义了。也就是说,不,默认情况下您不会获得透明运算符。
  • 你对命名有什么想法吗?我的意思是为什么is_transparent
  • 您想要一个“有用的具体示例”吗?这是my use case
【解决方案3】:

以下都是n3657的copy-pasta。

问。 让比较器“透明”的目的是什么?

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

问。 这解决了什么问题

A. LWG 担心以下代码:

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

在 C++11 中,这将构造一个临时的 std::string 然后 将其与元素进行比较以找到关键。

随着 N3465 提出的更改,std::set::find() 函数将 是一个不受约束的模板,它将通过 const char* 到比较器函数 std::less ,这将 为每次比较构造一个临时的 std::string 。工作组 认为这个性能问题是一个严重的问题。这 模板 find() 函数也会阻止在 指针容器,导致以前有效的代码不再 编译,但这被认为是一个没有沉默的问题那么严重的问题 性能回归

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

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

问。 默认集也会丢失其查找、计数等成员

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

引用Yakk

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

和 n3657,

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

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

full code is here

【讨论】:

  • std::set&lt;std::string&gt; 是否真的受益于“通过char const *”,或者您需要制作一个std::set&lt;std::string, std::less&lt;&gt;&gt;
  • @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&lt;std::string&gt;, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue.
  • 你的引用和我第 13 段的说法相反:“除非类型存在/不存在”...?!
  • @KerrekSB,这是我的错,N3657 应该说“存在”,但我写了“不存在”……这是最后一分钟写的迟到的论文。标准草案是正确的。
  • 是的,引用我的意思而不是我当时实际所说的话可能会更清楚:)
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 2022-09-28
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 2012-02-27
    • 1970-01-01
    相关资源
    最近更新 更多