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!

Answer

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.

Leave a Reply

Your email address will not be published. Required fields are marked *