为什么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?

查看:81
本文介绍了为什么const char []比std :: ranges :: range更好地匹配,而不是显式的const char *免费重载,以及如何解决它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为任何范围编写一个通用的<< ,最后我得出了以下结论:

  std :: ostream&运算符<< (std :: ostream& out,std :: ranges :: range auto&& range){
使用命名空间std :: ranges;

if(empty(range)){
return<< [];
}

自动电流=开始(范围);
out<< ’[’<< *当前;

while(++ current!= end(range)){
out<< ’,’<< *当前;
}

返回<< ’]’;
}

经过如下测试:

  int main(){
std :: vector< int> ints = {1,2,3,4};
std :: cout<<整数<< ‘n’;
}

它可以正常工作并输出:


  [1,2,3,4] 


但是,当使用以下命令进行测试时:

  int main(){
std ::向量< int>空= {};
std :: cout<<空<< ‘n’;
}

它意外输出:


  [[,],] 


使用调试器运行此代码,我得出的结论是,空范围的问题是我们运行 return out<< []; 。某些C ++魔术师决定我刚刚写的

  std :: ostream&运算符<< (std :: ostream& out,std :: ranges :: range auto&& range); 

更好匹配,然后是< ostream>

$中提供b $ b

  template<性状> 
basic_ostream< char,Traits>&运算符<((basic_ostream< char,Traits& os,
const char * s);

所以不只是发送 [] 到就像我们以前所看到的那样,它返回到自身,但是使用 [[] 作为范围参数。


匹配更好的原因是什么?与分别发送 [] 相比,我能以更优雅的方式解决此问题吗?




编辑:似乎这很可能是GCC 10.1.0中的错误,因为新版本拒绝代码。

解决方案

我认为这个不应该编译。让我们将示例简化为:

  template< typename T> struct basic_thing {}; 
使用concrete_thing = basic_thing< char> ;;

模板< typename T>概念C =真;

void f(concrete_thing,C auto&); //#1
模板< typename T>无效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拒绝将其视为模棱两可:演示




无论哪种方式,这里都有一个简单的解决方法,就是将 [然后是] 写为单独的字符。 / p>

无论哪种方式,您可能都不想写

  std :: ostream&运算符<< (std :: ostream& out,std :: ranges :: range auto&& range); 

首先,因为要使其真正正常工作,您必须将其粘贴在命名空间 std 。相反,您想编写一个用于任意范围的包装器,并使用它:

  template< input_range V>需要查看< V> 
struct print_view:view_interface< print_view< V>> {
print_view()=默认值;
print_view(V v):v(v){}

auto begin()const {return std :: ranges :: begin(v); }
auto end()const {return std :: ranges :: end(v); }

V v;
};

模板< range R>
print_view(R& r)-> print_view< all_t< R>> ;;

并定义您的 operator<< 来打印 print_view 。这样,它就可以工作,而您不必处理这些问题。 演示


当然,而不是出<< * current; ,您可能需要有条件地将其包装在 out<< print_view {* current}; 是完全正确的,但我将保留它作为练习。


I wanted to write a generic << for any range and I ended up with this:

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 << ']';
}

Tested like so:

int main() {
    std::vector<int> ints = {1, 2, 3, 4};
    std::cout << ints << '\n';
}

it works perfectly and outputs:

[1,2,3,4]

But, when tested with:

int main() {
    std::vector<int> empty = {};
    std::cout << empty << '\n';
}

it outputs, unexpectedly:

[[,], ]

Running this code with a debugger, I came to a conclusion that the problem with empty range is that we run the return out << "[]";. Some C++ magic decided that my, just written,

std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);

is a better match then the, provided in <ostream>,

template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,  
                                        const char* s );

so instead of just sending "[]" to the output stream like we are used to see, it recurses back to itself, but with "[]" as the range argument.

What is the reason for that being a better match? Can I fix this in a more elegant manner compared to sending [ and ] separately?


EDIT: It appears that this is most likely a bug in GCC 10.1.0, since the newer versions reject the code.

解决方案

I think this shouldn't compile. Let's simplify the example a bit to:

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{}, "");
}

The basic_thing/concrete_thing mimics what's going on with basic_ostream/ostream. #1 is the overload you're providing, #2 is the one in the standard library.

Clearly both of these overloads are viable for the call we're making. Which one is better?

Well, they're both exact matches in both arguments (yes, char const* is an exact match for "" even though we're undergoing pointer decay, see Why does pointer decay take priority over a deduced template?). So the conversion sequences can't differentiate.

Both of these are function templates, so can't differentiate there.

Neither function template is more specialized than the other - deduction fails in both directions (char const* can't match C auto&& and concrete_thing can't match basic_thing<T>).

The "more constrained" part only applies if the template parameter setup is the same in both cases, which is not true here, so that part is irrelevant.

And... that's it basically, we're out of tiebreakers. The fact that gcc 10.1 accepted this program was a bug, gcc 10.2 no longer does. Although clang does right now, and I believe that's a clang bug. MSVC rejects as ambiguous: Demo.


Either way, there's an easy fix here which is to write [ and then ] as separate characters.

And either way, you probably don't want to write

std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);

to begin with, since for that to actually work correctly you'd have to stick it in namespace std. Instead, you want to write a wrapper for an arbitrary range and use that instead:

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>>;

And define your operator<< to print a print_view. That way, this just works and you don't have to deal with these issues. Demo.

Of course, instead of out << *current; you may want to conditionally wrap that in out << print_view{*current}; to be totally correct, but I'll leave that as an exercise.

这篇关于为什么const char []比std :: ranges :: range更好地匹配,而不是显式的const char *免费重载,以及如何解决它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆