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.
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.
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With