可作为 const 调用的比较对象

Comparison object being invocable as const

提问人:Filipe Rodrigues 提问时间:7/9/2018 最后编辑:Filipe Rodrigues 更新时间:1/31/2023 访问量:15678

问:

当我尝试运行以下代码时,带有 -std=c++17 的 clang (6.0) 和 g++ (8) 都给我一个static_assert错误:

#include <set>
struct A {};

struct ProcessComparator { inline bool operator()(const A&, const A&) { return true; } };

int main(void)
{
    std::set<A, ProcessComparator> A_Set;

    return EXIT_SUCCESS;
}

g++ 8

/usr/bin/../lib/gcc/x86_64-linux-gnu/8/。/../../../include/c++/8/bits/stl_tree.h:457:7:错误:由于要求“is_invocable_v”比较对象必须可作为常量调用“,static_assert失败

叮叮当当 6.0

/usr/include/c++/8/bits/stl_tree.h:457:21:错误:静态断言失败:比较对象必须可作为 const 调用

将 const 作为 operator() 签名的一部分可以解决这个问题:

#include <set>

struct A {};

/* Add const as part of the operator's signature */
struct ProcessComparator { inline bool operator()(const A&, const A&) const { return true; } };

int main(void)
{
    std::set<A, ProcessComparator> A_Set;

    return EXIT_SUCCESS;
}

同时,使用 std=c++14 时,clang 和 g++ 中的错误都会消失。

我的问题是 c++17 中发生了什么变化,现在给出了一个错误,为什么这里的常量很重要?

const 只保证在 ProcessComparator 类中声明的每个对象都不会被修改(除了那些具有可变的对象),那么为什么这是一个要求呢?


这是静态断言失败的源代码中的源代码:

#if __cplusplus >= 201103L
      static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
      "comparison object must be invocable with two arguments of key type");
# if __cplusplus >= 201703L
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 2542. Missing const requirements for associative containers
      static_assert(is_invocable_v<const _Compare&, const _Key&, const _Key&>,
      "comparison object must be invocable as const");
# endif // C++17
#endif // C++11

添加了一个新static_assert,其中 Comparison 对象从 just to 和 to 更改为 ,尽管据我所知,这只是为了获得内联和 constexpr ,如下所示_Compare&<const _Compare&is_invocableis_invocable_v


我已经根据源代码注释找到了这个链接,但我仍然不明白为什么需要这样做。

C++ STL C++17

评论

7赞 Sam Varshavchik 7/9/2018
比较运算符应始终为 const。这条潜规则现在正在被积极执行。
0赞 Filipe Rodrigues 7/9/2018
@SamVarshavchik 好的,但是他们为什么要恒定呢?它有什么好处还是有某种历史原因?
4赞 Sam Varshavchik 7/9/2018
因为比较器类的实例是容器的一部分,并且您不能调用类实例的方法,除非该方法是 .constconst
0赞 Filipe Rodrigues 7/9/2018
@SamVarshavchik 这确实是有道理的,但为什么 c++14 允许这样做呢?是因为 c++14 中没有 const 方法,还是纯粹是疏忽了?
10赞 Sam Varshavchik 7/9/2018
好吧,它只是在某种意义上是“允许的”,如果你使用类方法,这些方法只在容器的可变实例上使用比较器类,它仍然有效。但是尝试在容器上使用它们仍然会失败,因为它从根本上是错误的。因此,这是严格执行正确性。这总是一件好事;我认为浪费时间弄清楚为什么它在 C++14 中仍然“允许”没有什么好处。这是完全无关紧要的。任何这样做的代码都已损坏,应该修复。constconst

答:

17赞 Macke 4/1/2019 #1

使运算符 const 保持原样(不允许可变状态):

struct ProcessComparator { inline bool operator()(const A&, const A&) const { return true; } };

如果跨线程并行运行此比较器,则恒定性对安全起见非常有用。默认情况下,它还可以防止可能导致问题的副作用,并允许编译器进行更多优化。如果 stdlib 允许运算符是非常量,它还应该假设存在某种状态正在修改(非常量),因此该访问可能不是线程安全的,或者它可能不会随意复制(并行访问)。

虽然编译器可能可以自己解决这个问题(但前提是内联),但库会强制执行这一点,以帮助您编写更正确和惯用的代码。

更新

如果您尝试聪明一点,并在比较运算符中缓存指向对象的指针,以便在多次比较相同的对象时进行更快的比较,则可能会发生这种情况(参见 f.ex. less than in many sorting ops)。

3赞 Marshall Clow 8/6/2019 #2

这并不是一个真正的答案,而是一个说明性的例子:

假设您有一个例程来测试您的集合是否包含特定值:

template <typename T>
bool contains(const std::set<T> &s, const T& value)
{ return s.find(value) != s.end(); }

如果你的比较函子不能作为 const 调用,那么这将无法编译,并显示可怕的错误消息。(即使在 C++11 和 14 中)