C++20概念要求运算符重载结合用户定义的模板运算符重载函数
C++20 concepts require operator overloading combine with user-define template operator overloading function
案例 1
考虑以下 concept
其中 requires
range
R
的 value_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
.
实际上,这些是相同的示例,只是名称不同(print
与 operator<<
),它们应该具有相同的行为。
案例 1
考虑以下 concept
其中 requires
range
R
的 value_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
.
实际上,这些是相同的示例,只是名称不同(print
与 operator<<
),它们应该具有相同的行为。