const char []が明示的なconstchar *フリーオーバーロードよりもstd :: ranges :: rangeに適しているのはなぜですか、またそれを修正する方法は?
私は<<
任意のジェネリックを書きたかったのですが、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);
でより良い試合は、その後で提供します
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
したがって"[]"
、これまでのように出力ストリームに送信するのではなく"[]"
、range
引数として、それ自体に再帰的に戻ります。
それがより良い一致である理由は何ですか?私は、送信に比べて、よりエレガントな方法でこの問題を解決することができ[
かつ]
個別に?
編集:新しいバージョンはコードを拒否するため、これはGCC10.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がこのプログラムを受け入れたという事実はバグでしたが、gcc10.2はもはや受け入れません。clangは今は機能しますが、それはclangのバグだと思います。MSVCはあいまいなものとして拒否します:デモ。
いずれにせよ、ここに簡単な修正があります。それは、書き込み[
を行ってから]
、別々の文字として書き込むことです。
そしてどちらにしても、あなたはおそらく書きたくないでしょう
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
。そうすれば、これはうまく機能し、これらの問題に対処する必要はありません。デモ。
もちろん、完全に正しいようout << *current;
に条件付きでラップするのでout << print_view{*current};
はなく、それを演習として残しておきます。