namespace N {
struct A {};
template<typename T>
constexpr bool operator<(const T&, const T&) { return true; }
}
constexpr bool operator<(const N::A&, const N::A&) { return false; }
#include<functional>
int main() {
static_assert(std::less<N::A>{}({}, {}), "assertion failed");
}
See https://godbolt.org/z/vsd3qfch6.
This program compiles on seemingly random versions of compilers.
The assertion fails on all versions of MSVC since v19.15, but succeeds on v19.14.
It succeeds on GCC 11.2 and before, but fails on current GCC trunk.
It fails on Clang with libstdc++ in all versions. It succeeds with libc++ in all versions, including current trunk, except version 13.
It always succeeds with ICC.
Is it specified whether or not the static_assert
should succeed?
The underlying issue here is that std::less
uses <
internally, which because it is used in a template will find operator<
overloads via argument-dependent lookup from the point of instantiation (which is the proper method), but also via unqualified name lookup from the point of definition of the template.
If the global overload is found, it is a better match. Unfortunately this make the program behavior dependent on the placement and order of the standard library includes.
I would have expected that the standard library disables unqualified name lookup outside the std
namespace, since it cannot be relied on anyway, but is that supposed to be guaranteed?
#include <functional>
. (As are all the compilers shown in your linked Compiler Explorer page!) – Kerato#include <functional>
is part of a header file not using any standard library includes. If#include <functional>
is put at the beginning then I am pretty sure that the assertion is guaranteed to succeed. – Uncloakstatic_assert
is not usingA
directly, but is together with the<functional>
include in another header file as a template instantiated inmain
. Then standard library headers can be at the top of each header, while the result will be dependent on the inclusion order of the two headers inmain.cpp
. – Uncloakless
is in the global namespace there is nothing to prevent that. However, ifless
is not in the global namespace, then the dependence on the location could be avoided by declaring an overload ofoperator<
with hidden, non-constructible types as parameters beforeless
in the same namespace. Then it wouldn't matter where they are placed (together). Question is why the standard library doesn't do this. – Uncloaknamespace test { class X; constexpr bool operator<(const X&, const X&); template<typename _Tp> struct less { constexpr bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } }; }
Presumably because they expect that the standard library headers are put at the top. – Motherly