问题描述
我想为任何<<
写一个通用的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,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*
是""
的完全匹配,即使我们正在经历指针衰减,请参见Why does pointer decay take priority over a deduced template?)。因此转换顺序无法区分。
这两个都是函数模板,因此无法在其中区分。
这两个功能模板都不比另一个更专业-推导在两个方向上都失败(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 << *current;
中而不是out << print_view{*current};
,但我将保留它作为练习。