为什么 const char[] 更适合 std::ranges::range 而不是显式的 const char* 自由重载,以及如何修复它?
Why is const char[] a better match for std::ranges::range than for an explicit, const char* free overload, and how to fix it?
我想为任何 range
写一个通用的 <<
,最后我得到了这个:
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range) {
using namespace std::ranges;
if (empty(range)) {
return out << "[]";
}
auto current = begin(range);
out << '[' << *current;
while(++current != end(range)) {
out << ',' << *current;
}
return out << ']';
}
像这样测试:
int main() {
std::vector<int> ints = {1, 2, 3, 4};
std::cout << ints << '\n';
}
它完美运行并输出:
[1,2,3,4]
但是,测试时:
int main() {
std::vector<int> empty = {};
std::cout << empty << '\n';
}
它意外地输出:
[[,], ]
运行这段代码用调试器,我得出的结论是空范围的问题是我们运行 return out << "[]";
。一些 C++ 魔法决定我的,刚刚写的,
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
比 更匹配 provided in <ostream>
,
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
所以它不像我们过去看到的那样只是将 "[]"
发送到输出流,而是递归回到自身,但是使用 "[]"
作为 range
参数。
匹配更好的原因是什么?与分别发送 [
和 ]
相比,我能否以更优雅的方式解决此问题?
编辑:看来这很可能是 GCC 10.1.0 中的错误,因为较新的版本 reject 代码。
我认为这个不应该编译。让我们将示例稍微简化为:
template <typename T> struct basic_thing { };
using concrete_thing = basic_thing<char>;
template <typename T> concept C = true;
void f(concrete_thing, C auto&&); // #1
template <typename T> void f(basic_thing<T>, char const*); // #2
int main() {
f(concrete_thing{}, "");
}
basic_thing
/concrete_thing
模仿了 basic_ostream
/ostream
的情况。 #1
是您提供的重载,#2
是标准库中的重载。
很明显,这两个重载对于我们正在进行的调用都是可行的。哪个更好?
好吧,它们在两个参数中都完全匹配(是的,char const*
与 ""
完全匹配,即使我们正在进行指针衰减,请参阅 ) .所以转换序列无法区分
这两个都是函数模板,无法区分。
两个函数模板都不比另一个更专业 - 推导在两个方向上都失败(char const*
无法匹配 C auto&&
并且 concrete_thing
无法匹配 basic_thing<T>
).
“更多约束”部分仅适用于两种情况下模板参数设置相同的情况,但此处并非如此,因此该部分无关紧要。
而且...基本上就是这样,我们没有决胜局。 gcc 10.1 接受这个程序的事实是一个错误,gcc 10.2 不再接受。虽然现在 clang 确实如此,但我相信这是一个 clang 错误。 MSVC 拒绝为不明确:Demo.
无论如何,这里有一个简单的解决方法,就是将 [
和 ]
分别写成单独的字符。
无论哪种方式,您可能都不想写
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
首先,因为要使其真正正常工作,您必须将其粘贴在命名空间 std
中。相反,您想为任意范围编写包装器并改用它:
template <input_range V> requires view<V>
struct print_view : view_interface<print_view<V>> {
print_view() = default;
print_view(V v) : v(v) { }
auto begin() const { return std::ranges::begin(v); }
auto end() const { return std::ranges::end(v); }
V v;
};
template <range R>
print_view(R&& r) -> print_view<all_t<R>>;
并定义您的 operator<<
以打印 print_view
。这样,这就可以正常工作,您不必处理这些问题。 Demo.
当然,您可能希望有条件地将其包装在 out << print_view{*current};
中而不是 out << *current;
以完全正确,但我会把它留作练习。
我想为任何 range
写一个通用的 <<
,最后我得到了这个:
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range) {
using namespace std::ranges;
if (empty(range)) {
return out << "[]";
}
auto current = begin(range);
out << '[' << *current;
while(++current != end(range)) {
out << ',' << *current;
}
return out << ']';
}
像这样测试:
int main() {
std::vector<int> ints = {1, 2, 3, 4};
std::cout << ints << '\n';
}
它完美运行并输出:
[1,2,3,4]
但是,测试时:
int main() {
std::vector<int> empty = {};
std::cout << empty << '\n';
}
它意外地输出:
[[,], ]
运行这段代码用调试器,我得出的结论是空范围的问题是我们运行 return out << "[]";
。一些 C++ 魔法决定我的,刚刚写的,
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
比 更匹配 provided in <ostream>
,
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
所以它不像我们过去看到的那样只是将 "[]"
发送到输出流,而是递归回到自身,但是使用 "[]"
作为 range
参数。
匹配更好的原因是什么?与分别发送 [
和 ]
相比,我能否以更优雅的方式解决此问题?
编辑:看来这很可能是 GCC 10.1.0 中的错误,因为较新的版本 reject 代码。
我认为这个不应该编译。让我们将示例稍微简化为:
template <typename T> struct basic_thing { };
using concrete_thing = basic_thing<char>;
template <typename T> concept C = true;
void f(concrete_thing, C auto&&); // #1
template <typename T> void f(basic_thing<T>, char const*); // #2
int main() {
f(concrete_thing{}, "");
}
basic_thing
/concrete_thing
模仿了 basic_ostream
/ostream
的情况。 #1
是您提供的重载,#2
是标准库中的重载。
很明显,这两个重载对于我们正在进行的调用都是可行的。哪个更好?
好吧,它们在两个参数中都完全匹配(是的,char const*
与 ""
完全匹配,即使我们正在进行指针衰减,请参阅
这两个都是函数模板,无法区分。
两个函数模板都不比另一个更专业 - 推导在两个方向上都失败(char const*
无法匹配 C auto&&
并且 concrete_thing
无法匹配 basic_thing<T>
).
“更多约束”部分仅适用于两种情况下模板参数设置相同的情况,但此处并非如此,因此该部分无关紧要。
而且...基本上就是这样,我们没有决胜局。 gcc 10.1 接受这个程序的事实是一个错误,gcc 10.2 不再接受。虽然现在 clang 确实如此,但我相信这是一个 clang 错误。 MSVC 拒绝为不明确:Demo.
无论如何,这里有一个简单的解决方法,就是将 [
和 ]
分别写成单独的字符。
无论哪种方式,您可能都不想写
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
首先,因为要使其真正正常工作,您必须将其粘贴在命名空间 std
中。相反,您想为任意范围编写包装器并改用它:
template <input_range V> requires view<V>
struct print_view : view_interface<print_view<V>> {
print_view() = default;
print_view(V v) : v(v) { }
auto begin() const { return std::ranges::begin(v); }
auto end() const { return std::ranges::end(v); }
V v;
};
template <range R>
print_view(R&& r) -> print_view<all_t<R>>;
并定义您的 operator<<
以打印 print_view
。这样,这就可以正常工作,您不必处理这些问题。 Demo.
当然,您可能希望有条件地将其包装在 out << print_view{*current};
中而不是 out << *current;
以完全正确,但我会把它留作练习。