c++ - 为什么 const char[] 更适合 std::ranges::range 而不是显式的 const char* 自由重载,以及如何解决它?
问题描述
我想<<
为任何人写一个通用的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);
是更好的匹配,然后提供<ostream>
,
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
因此,它不像我们通常看到的那样仅发送"[]"
到输出流,而是递归回自身,但"[]"
作为range
参数。
那是更好的匹配的原因是什么?[
与]
单独发送相比,我可以以更优雅的方式解决此问题吗?
编辑:这似乎很可能是 GCC 10.1.0 中的一个错误,因为较新的版本拒绝该代码。
解决方案
我认为这不应该编译。让我们稍微简化一下这个例子:
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);
首先,因为要真正正常工作,您必须将其粘贴在 namespace 中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
. 这样,它就可以工作,而您不必处理这些问题。演示。
当然,out << *current;
您可能希望有条件地将其包装out << print_view{*current};
为完全正确的,但我将把它作为练习。
推荐阅读
- javascript - JS不会将POST请求中的数据传给php文件,否则php收不到
- c++ - 可能的无限循环
- java - 除非我按“结束”然后程序结束,否则我如何制作这个循环以使输出与输入相同?
- cobol - 如何在 COBOL 中将程序输出到 HTML 文件中
- python - 石头剪刀布,但等等!它的设置不同
- javascript - Node.js GET、POST、DELETE 方法
- python - Django:在进行动态 Q OR 模型查询时解包的值太多(预期为 2)
- c - 如何使用 gcc 制作 .o?
- rss - 有什么区别
和 播客 RSS 源? - sql - 使用流标识符键更新记录