Perché const char [] è una corrispondenza migliore per std :: range :: range rispetto a un sovraccarico esplicito, const char * free, e come risolverlo?
Volevo scrivere un generico <<
per qualsiasi range
e ho finito con questo:
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 << ']';
}
Testato in questo modo:
int main() {
std::vector<int> ints = {1, 2, 3, 4};
std::cout << ints << '\n';
}
funziona perfettamente e produce:
[1,2,3,4]
Ma , se testato con:
int main() {
std::vector<int> empty = {};
std::cout << empty << '\n';
}
emette, inaspettatamente:
[[,], ]
Eseguendo questo codice con un debugger, sono giunto alla conclusione che il problema con l'intervallo vuoto è che eseguiamo il return out << "[]";
. Un po 'di magia C ++ ha deciso che il mio, appena scritto,
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
è una migliore corrispondenza poi il, fornita in
template< class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
const char* s );
quindi invece di inviare semplicemente "[]"
al flusso di output come siamo abituati a vedere, ricorre a se stesso, ma con "[]"
l' range
argomento.
Qual è il motivo per cui è una partita migliore? Posso risolvere questo problema in modo più elegante rispetto all'invio [
e ]
separatamente?
EDIT: Sembra che questo sia molto probabilmente un bug in GCC 10.1.0, poiché le versioni più recenti rifiutano il codice.
Risposte
Penso che questo non dovrebbe essere compilato. Semplifichiamo un po 'l'esempio per:
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{}, "");
}
I basic_thing
/ concrete_thing
imita quello che sta succedendo con basic_ostream
/ ostream
. #1
è il sovraccarico che stai fornendo, #2
è quello nella libreria standard.
Chiaramente entrambi questi sovraccarichi sono vitali per la chiamata che stiamo facendo. Quale è la migliore?
Bene, sono entrambe le corrispondenze esatte in entrambi gli argomenti (sì, char const*
è una corrispondenza esatta ""
anche se stiamo subendo il decadimento del puntatore, vedi Perché il decadimento del puntatore ha la priorità su un modello dedotto? ). Quindi le sequenze di conversione non possono differenziarsi.
Entrambi sono modelli di funzioni, quindi non è possibile differenziarli.
Nessuno dei due modelli di funzione è più specializzato dell'altro: la deduzione fallisce in entrambe le direzioni ( char const*
non può corrispondere C auto&&
e concrete_thing
non può corrispondere basic_thing<T>
).
La parte "più vincolata" si applica solo se l'impostazione dei parametri del modello è la stessa in entrambi i casi, il che non è vero qui, quindi quella parte è irrilevante.
E ... fondamentalmente è tutto, abbiamo finito i tiebreak. Il fatto che gcc 10.1 accettasse questo programma era un bug, ma gcc 10.2 non lo fa più. Anche se il clang adesso funziona, e credo che sia un bug di clang. MSVC rifiuta come ambiguo: Demo .
Ad ogni modo, c'è una soluzione semplice qui che è scrivere [
e poi ]
come caratteri separati.
E in ogni caso, probabilmente non vuoi scrivere
std::ostream& operator << (std::ostream& out, std::ranges::range auto&& range);
per cominciare, dal momento che affinché funzioni correttamente dovresti inserirlo nello spazio dei nomi std
. Invece, vuoi scrivere un wrapper per un intervallo arbitrario e usarlo invece:
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>>;
E definisci il tuo operator<<
per stampare un file print_view
. In questo modo, funziona e non devi affrontare questi problemi. Demo .
Ovviamente, invece di out << *current;
te, potresti volerlo racchiudere condizionatamente out << print_view{*current};
per essere totalmente corretto, ma lo lascerò come esercizio.