康桓瑋*_*康桓瑋 4 c++ templates operator-overloading language-lawyer c++20
考虑下面concept这requires的value_type的range 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 错误。
古怪的,如果我定义了一个自定义的结构S与operator<<支持前的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 错误吗?
如果我将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)
我应该信任哪个编译器?这似乎是一个 Clang 错误。
这是一个 GCC/MSVC 错误。名称查找os << x将执行依赖于参数的查找以查找任何其他关联的operator<<s,但这里关联的命名空间只是std. 你operator<<是不是在namespace std,所以查找不应该找到它,所以应该没有可行的候选人。
GCC 和 MSVC 这样做的事实是一个错误。
GCC 的问题在于,它与运算符的查找,特别是,只会发现比它应该的更多的东西(见51577,感谢 TC)。这就是为什么它可以找到operator<<但不能找到print.
实际上,这些是相同的示例,只是名称不同(printvs operator<<)并且它们应该具有相同的行为。
叮当是对的。这是通常的两阶段查找规则,已知 GCC 会为操作员错误地处理(并且 MSVC 也不完全知道正确的两阶段查找支持,尽管它们正在变得更好)。
普通的非限定查找operator<<只发生在定义上下文中,什么也没找到。依赖参数的查找也无法operator<<在全局命名空间中找到,因为全局命名空间不是 的关联命名空间std::vector<int>。