Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparison object being invocable as const

Tags:

c++

c++17

stl

When I try to run the following code, both clang (6.0) and g++ (8) with -std=c++17 give me a static_assert error:

#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: error: static_assert failed due to requirement 'is_invocable_v' "comparison object must be invocable as const"

clang 6.0

/usr/include/c++/8/bits/stl_tree.h:457:21: error: static assertion failed: comparison object must be invocable as const

Putting a const as part of the operator() signature fixes this problem:

#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;
}

Meanwhile with std=c++14 the error goes away in both clang and g++.

My question is what changed in c++17 for this to now give an error and why does the const here matter?

The const only guarantees that every object declared inside the ProcessComparator class doesn't get modified (aside from those with mutable), so why is this a requirement?


This is the source code from the source code where the static assert fails:

#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

A new static_assert was added where the Comparison object was changed from just _Compare&< to const _Compare& and is_invocable to is_invocable_v, although that, from what I can understand, is just to gain inline and constexpr as seen here


I've found this link, based on the source code comment, but I still cannot understand why this is required.

like image 612
Filipe Rodrigues Avatar asked Jul 08 '18 19:07

Filipe Rodrigues


People also ask

Is it allowed to use comparators on const containers?

Well, it was only "allowed" in a sense that if you used class methods which make use of the comparator class only on mutable instances of the container, it would still work. But attempting to use them on const containers will still fail, because it would be fundamentally ill-formed. As such, this is strictly enforcing const -correctness.

Is there any reason to make the comparator operator const?

It's completely irrelevant. Any code that did that was broken, and should be fixed. Make operator const, as it should be (no mutable state allowed): If you run this comparator in parallel across threads, constness is nice for safety. It also prevents weird side-effects and allows the compiler to optimize things more, by default.

Why const for processcomparator objects?

The const only guarantees that every object declared inside the ProcessComparator class doesn't get modified (aside from those with mutable), so why is this a requirement? This is the source code from the source code where the static assert fails:


1 Answers

Make operator const, as it should be (no mutable state allowed):

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

If you run this comparator in parallel across threads, constness is nice for safety. It also prevents weird side-effects and allows the compiler to optimize things more, by default. If the stdlib allowed the operator to be non-const, it should also assume that there is some state there being modified (non-const) and thus that access might not be thread-safe or that it might not make copies willy-nilly (parallel access).

While the compiler probably can figure this it out on its own (but only if inlined), the library enforces this to help you write more correct and idiomatic code.

like image 174
Macke Avatar answered Sep 21 '22 01:09

Macke