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?