Consider the following code, a simple Struct with all of the pre-C++20 comparison operators defined, as well as a conversion operator to const char *
(that for this example throws, for simplicity of tracing).
struct Struct
{
int _i;
Struct( int i ) : _i( i ) {}
bool operator==( const Struct &b ) const { return _i == b._i; }
bool operator!=( const Struct &b ) const { return _i != b._i; }
bool operator<( const Struct &b ) const { return _i < b._i; }
bool operator<=( const Struct &b ) const { return _i <= b._i; }
bool operator>( const Struct &b ) const { return _i > b._i; }
bool operator>=( const Struct &b ) const { return _i >= b._i; }
operator const char *() const { throw "Crash"; return nullptr; }
};
Now let us put that structure into a std::tuple
, just a single element for simplicity's sake. Create two of those and sort them (lexicographically, per std::tuple
):
#include <cstdio>
#include <tuple>
int main()
{
std::tuple<Struct> a( 1 ), b( 2 );
printf( "%s\n", a < b ? "Right" : "Wrong" );
return 0;
}
What does this output? Under C++17 it will print "Right"
as you may expect. Under C++20, though, the same code will throw the exception in the const char *
conversion operator.
Why? Because we haven't defined an operator <=>
in our struct, and C++20's std::tuple<Struct>
will end up calling std::operator<=><Struct, Struct>
in order to determine whether a < b
is true. Per the C++20 standard, std::tuple
only defines the operator ==
and operator <=>
comparison operators, which the compiler then uses to perform the <
operation.
What's surprising is std::operator<=><Struct, Struct>
ends up being producing code equivalent to (const char *) <=> (const char *)
. It ignores the Struct
comparison operators that could have otherwise been used to synthesize operator <=>
, in favor of the conversion operator.
In practice this means that our std::tuple<Struct>
had a well-defined ordering in C++17 that now takes a different codepath through the operator const char *
conversion, which results in different behavior at run-time.
My question:
Other than manually looking at all instantiations of std::tuple
and verifying that either lexicographic comparisons are not performed, there are no conversion operators, or that any classes or structures contained within define operator <=>
, is there a way to identify at compile-time that this problem exists in a large codebase?