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

康桓瑋*_*康桓瑋 4 c++ templates operator-overloading language-lawyer c++20

情况1

考虑下面conceptrequiresvalue_typerange R是打印:

#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; };
Run Code Online (Sandbox Code Playgroud)

它适用std::vector<int>于不同的三个编译器:

static_assert(printable_range<std::vector<int>>);
Run Code Online (Sandbox Code Playgroud)

但是如果我在定义之后定义一个operator<<任何类型的模板函数:xconcepts

std::ostream& operator<<(std::ostream& os, const auto& x) { return os << x; }
Run Code Online (Sandbox Code Playgroud)

GCC 和 MSVC 可以通过以下断言,但 Clang失败

static_assert(printable_range<std::vector<std::vector<int>>>);
Run Code Online (Sandbox Code Playgroud)

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

案例2

古怪的,如果我定义了一个自定义的结构Soperator<<支持前的concept printable_range定义:

struct S{};
std::ostream& operator<<(std::ostream& os, const S&) { return os; }
Run Code Online (Sandbox Code Playgroud)

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

static_assert(printable_range<std::vector<std::vector<int>>>);
Run Code Online (Sandbox Code Playgroud)

这是一个 MSVC 错误吗?

案例3

如果我将operator<<函数转换为命名函数print,则所有编译器在第二个断言时都会失败。这让我感到惊讶,因为它看起来等同于案例 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!
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 6

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

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

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

GCC 的问题在于,它与运算符的查找,特别是,只会发现比它应该的更多的东西(见51577,感谢 TC)。这就是为什么它可以找到operator<<但不能找到print.

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

  • https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51577 (2认同)
  • @cigien番茄,otamot,... (2认同)

T.C*_*.C. 5

叮当是对的。这是通常的两阶段查找规则,已知 GCC 会为操作员错误地处理(并且 MSVC 也不完全知道正确的两阶段查找支持,尽管它们正在变得更好)。

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