Reputation: 42766
Consider the following concept
which requires
the value_type
of a range
R
is printable:
#include <iostream>
#include <iterator>
template <class R, typename T = std::ranges::range_value_t<R>>
concept printable_range = requires(std::ostream& os, const T& x) { os << x; };
It works fine with std::vector<int>
on different three compliers:
static_assert(printable_range<std::vector<int>>);
but if I define a template operator<<
function with any type x
after concepts
define:
std::ostream& operator<<(std::ostream& os, const auto& x) { return os << x; }
GCC and MSVC can pass the following assert but Clang fails:
static_assert(printable_range<std::vector<std::vector<int>>>);
Which compiler should I trust? It seems like a Clang bug.
Weirdly, If I define a custom struct S
with operator<<
support before the concept
printable_range
define:
struct S{};
std::ostream& operator<<(std::ostream& os, const S&) { return os; }
Same assert fails with MSVC, but GCC still accept it:
static_assert(printable_range<std::vector<std::vector<int>>>);
Is it an MSVC bug?
If I transform the operator<<
function into a named function print
, then all the compiler fails on the second assert. This surprised me since it looks equivalent to case 1, the key points here are the member function vs. free function or operator overloading function vs. free function?
void print(int x) { std::cout << x; };
template <class R, typename T = std::ranges::range_value_t<R>>
concept printable_range = requires(const T& x) { print(x); };
void print(auto x) { std::cout << x; };
static_assert(printable_range<std::vector<int>>);
static_assert(printable_range<std::vector<std::vector<int>>>); // failed!
Upvotes: 4
Views: 523
Reputation: 302932
Which compiler should I trust? It seems like a Clang bug.
This is a GCC/MSVC bug. Name lookup for os << x
will perform argument-dependent lookup to find any other associated operator<<
s, but the associated namespaces here are just std
. Your operator<<
is not in namespace std
, so lookup should not find it, so there should be no viable candidates.
The fact that GCC and MSVC do so is a bug.
The issue with GCC is that its lookup with operators, specifically, just finds more things than it should (see 51577, thanks T.C.). That's why it can find the operator<<
but not the print
.
Really, these are the same example, just with a different name (print
vs operator<<
) and they should have the same behavior.
Upvotes: 6
Reputation: 137330
Clang is correct. This is the usual two-phase lookup rule that GCC is known to handle incorrectly for operators (and MSVC is also not exactly known for proper two-phase lookup support, though they are getting better).
Ordinary unqualified lookup for operator<<
only occurs from the definition context and finds nothing. Argument-dependent lookup can't find the operator<<
in the global namespace either, since the global namespace is not an associated namespace of std::vector<int>
.
Upvotes: 5