C++20概念要求运算符重载结合用户定义的模板运算符重载函数

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

案例 1

考虑以下 concept 其中 requires range Rvalue_type 是可打印的:

#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; };

它与 std::vector<int> 在不同的三个编译器上工作正常:

static_assert(printable_range<std::vector<int>>);

但是如果我在 concepts 之后定义一个具有任何类型 x 的模板 operator<< 函数,则定义:

std::ostream& operator<<(std::ostream& os, const auto& x) { return os << x; }

GCC 和 MSVC 可以传递以下断言,但 Clang fails:

static_assert(printable_range<std::vector<std::vector<int>>>);

我应该信任哪个编译器?这似乎是一个 Clang 错误。

案例 2

奇怪的是,如果我在 concept printable_range 定义之前定义了一个具有 operator<< 支持的自定义结构 S define:

struct S{};
std::ostream& operator<<(std::ostream& os, const S&) { return os; }

MSVC 的相同断言失败,但 GCC 仍然 accept 它:

static_assert(printable_range<std::vector<std::vector<int>>>);

这是 MSVC 错误吗?

案例3

如果我将 operator<< 函数转换为命名函数 print,那么所有的编译器 fails 都放在第二个断言上。这让我感到惊讶,因为它看起来等同于案例 1,这里的关键点是 成员函数 自由函数 运算符重载函数自由函数?

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!

Clang 是正确的。这是通常的两阶段查找规则,已知 GCC 无法正确处理运算符(并且 MSVC 也并不完全知道正确的两阶段查找支持,尽管它们正在变得更好)。

operator<< 的普通非限定查找只发生在定义上下文中,什么也找不到。依赖于参数的查找也无法在全局命名空间中找到 operator<<,因为全局命名空间不是 std::vector<int>.

的关联命名空间

Which compiler should I trust? It seems like a Clang bug.

这是一个 GCC/MSVC 错误。 os << x 的名称查找将执行依赖于参数的查找以查找任何其他关联的 operator<<,但此处关联的名称空间只是 std。您的 operator<<namespace std 不是 ,因此查找应该找不到它,因此应该没有可行的候选者。

GCC 和 MSVC 这样做是一个错误。

GCC 的问题在于它使用运算符的查找,具体来说,只是找到了比它应该找到的更多的东西(参见 51577,感谢 T.C)。这就是为什么它可以找到 operator<< 而不是 print.

实际上,这些是相同的示例,只是名称不同(printoperator<<),它们应该具有相同的行为。