康桓瑋
康桓瑋

Reputation: 42766

C++20 concepts require operator overloading combine with user-define template operator overloading function

case 1

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.

case 2

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?

case3

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

Answers (2)

Barry
Barry

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

T.C.
T.C.

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

Related Questions