2014-04-27 14 views
5

Dopo la domanda How can I detect if a type can be streamed to an std::ostream? Ho scritto una classe di caratteri che dice se alcuni tipi possono essere trasmessi in streaming a un flusso IO. Il tratto ha funzionato bene fino ad ora che ho scoperto un problema.Perché il mio operatore di ricerca della classe dei modelli dei tratti non è << per llvm :: StringRef?

Sto usando il codice all'interno di un progetto che utilizza LLVM e sto usando la loro classe StringRef (che è simile nello spirito alla proposta std :: string_view). Here è un collegamento al doc Doxygen per la classe, da cui è possibile trovare il suo file di intestazione della dichiarazione, se necessario. Dal momento che LLVM non fornisce un operatore < < per lo streaming di oggetti StringRef ai flussi std (utilizzano una classe di flusso leggero personalizzata), ne ho scritto uno.

Tuttavia, quando uso il tratto non funziona se il mio operatore personalizzato < < è dichiarato dopo il tratto (questo accade perché ho il tratto in un colpo di testa e l'operatore < < funzione in un altro) . Ero solito pensare che la ricerca nelle istanze dei template funzionasse dal punto di vista del punto di istanziazione, quindi ho pensato che avrebbe dovuto funzionare. In realtà, come puoi vedere qui sotto, con un'altra classe e il suo operatore personalizzato < <, dichiarato dopo il tratto, tutto funziona come previsto (ecco perché ho scoperto questo problema solo ora), quindi non riesco a capire cosa rende StringRef speciale.

Questo è l'esempio completo:

#include <iostream> 

#include "llvm/ADT/StringRef.h" 

// Trait class exactly from the cited question's accepted answer 
template<typename T> 
class is_streamable 
{ 
    template<typename SS, typename TT> 
    static auto test(int) 
     -> decltype(std::declval<SS&>() << std::declval<TT>(), 
        std::true_type()); 

    template<typename, typename> 
    static auto test(...) -> std::false_type; 

public: 
    static const bool value = decltype(test<std::ostream,T>(0))::value; 
}; 

// Custom stream operator for StringRef, declared after the trait 
inline std::ostream &operator<<(std::ostream &s, llvm::StringRef const&str) { 
    return s << str.str(); 
} 

// Another example class 
class Foo { }; 
// Same stream operator declared after the trait 
inline std::ostream &operator<<(std::ostream &s, Foo const&) { 
    return s << "LoL\n"; 
} 

int main() 
{ 
    std::cout << std::boolalpha << is_streamable<llvm::StringRef>::value << "\n"; 
    std::cout << std::boolalpha << is_streamable<Foo>::value << "\n"; 

    return 0; 
} 

Contrariamente alle mie aspettative, questo stampe:

false 
true 

Se sposto la dichiarazione dell'operatore < < per stringRef prima della dichiarazione trait , stampa vero. Quindi perché sta succedendo questa cosa strana e come posso risolvere questo problema?

+0

Metti la tua operatore nello stesso spazio dei nomi come il tipo per consentire ADL. – Yakk

+0

@Yakk Questa è la risposta, quindi perché non scriverne una? – jrok

+1

@jrok perché sto facendo addormentare un bambino per un pisolino, e questo non è facile da controllare è il vero problema e l'elaborazione ecc. :) – Yakk

risposta

1

Come accennato da Yakk, questo è semplicemente ADL: Argomento di ricerca dipendente.

Se non vuoi disturbarti, ricorda che dovresti sempre scrivere una funzione libera nello stesso spazio dei nomi di almeno uno dei suoi argomenti. Nel tuo caso, poiché è vietato aggiungere funzioni a std, significa aggiungere la tua funzione nello spazio dei nomi llvm. Il fatto che tu avessi bisogno di qualificare l'argomento StringRef con llvm:: era un dato negativo.

Le regole di risoluzione funzione sono abbastanza complesso, ma come un rapido schizzo:

  • ricerca del nome: raccoglie una serie di potenziali candidati risoluzione
  • sovraccarico: raccoglie il miglior candidato tra i potenziali
  • risoluzione della specializzazione: se il candidato è un modello di funzione, verificare eventuali specializzazioni applicabili

La fase di ricerca del nome che siamo con qui è relativamente semplice. In breve:

  • scansiona spazi dei nomi dell'argomento, poi i loro genitori, ...fino a raggiungere la portata globale
  • procede poi attraverso la scansione l'ambito corrente, quindi la sua portata genitore, ... fino a raggiungere la portata globale

Probabilmente to allow shadowing (come per qualsiasi altra ricerca del nome), la ricerca si ferma al primo ambito in cui incontra una partita e ignora altezzosamente qualsiasi portata circostante.

Si noti che le direttive using (using ::operator<<; per esempio) possono essere utilizzate per introdurre un nome da un altro ambito. È gravoso però, poiché mette l'onere sul cliente, quindi per favore non fare affidamento sulla sua disponibilità come scusa per la sciatteria (che ho visto fare: x).


Esempio di shadowing: questa stampa "Hello, World" senza generare un errore di ambiguità.

#include <iostream> 

namespace hello { namespace world { struct A{}; } } 

namespace hello { void print(world::A) { std::cout << "Hello\n"; } } 

namespace hello { namespace world { void print(A) { std::cout << "Hello, World\n"; } } } 

int main() { 
    hello::world::A a; 
    print(a); 
    return 0; 
} 

Esempio di interrupted search: ::hello::world ha prodotto una funzione denominata print così è stato scelto anche se non corrisponde a tutti e ::hello::print sarebbe stato un match strettamente migliore.

#include <iostream> 

namespace hello { namespace world { struct A {}; } } 

namespace hello { void print(world::A) { } } 

namespace hello { namespace world { void print() {} } }; 

int main() { 
    hello::world::A a; 
    print(a); // error: too many arguments to function ‘void hello::world::print()’ 
    return 0; 
}