2014-04-27 3 views
5

Nach der Frage How can I detect if a type can be streamed to an std::ostream? habe ich eine Merkmalsklasse geschrieben, die besagt, ob ein Typ in einen IO-Stream gestreamt werden kann. Das Merkmal scheint bis jetzt gut zu funktionieren, dass ich ein Problem entdeckt habe.Warum kann mein Trait Template-Klassen-Suchoperator << für llvm :: StringRef nicht?

Ich verwende den Code in einem Projekt, das LLVM verwendet, und ich benutze ihre StringRef-Klasse (die im Sinne der vorgeschlagenen std :: string_view ähnlich ist). Here ist ein Link zum Doxygen-Dokument für die Klasse, von dem Sie bei Bedarf die Deklarationsheaderdatei finden können. Da LLVM keinen Operator < < zum Streamen von StringRef-Objekten an Std-Streams bereitstellt (sie verwenden eine benutzerdefinierte Lightweight Stream-Klasse), habe ich eine geschrieben.

Allerdings, wenn ich das Merkmal verwenden funktioniert es nicht, wenn mein benutzerdefinierter Operator < < nach das Merkmale erklärt wird (dies geschieht, weil ich das Merkmal in einem Header und die Bediener habe < < Funktion in einer anderen) . Ich dachte früher, dass das Nachschlagen in Template-Instanziierungen vom Standpunkt des Instanziierungspunkts aus funktioniert, also dachte ich, es sollte funktionieren. Wie Sie unten sehen können, funktioniert eine andere Klasse und ihr benutzerdefinierter Operator < < nach dem Merkmal, alles funktioniert wie erwartet (deshalb habe ich dieses Problem erst jetzt entdeckt), also kann ich nicht herausfinden, was StringRef ausmacht Besondere.

Dies ist die komplette Beispiel:

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

Entgegen meinen Erwartungen, diese Drucke:

false 
true 

Wenn ich die Erklärung des Betreibers < < für StringRef vor die Eigenschaft Erklärung bewegen , es druckt wahr. Also warum passiert dieses seltsame Ding und wie kann ich dieses Problem beheben?

+0

Setzen Sie Ihren Operator in denselben Namespace wie den Typ, um ADL zu aktivieren. – Yakk

+0

@Yakk Das ist die Antwort, also warum nicht eine schreiben? – jrok

+1

@jrok, weil ich ein Baby für ein Nickerchen schlafen legen, und das ist nicht günstig zu überprüfen, es ist das echte Problem und Ausarbeitung usw. :) – Yakk

Antwort

1

Wie von Yakk erwähnt, ist dies einfach ADL: Argument Dependent Lookup.

Wenn Sie nicht stören möchten, denken Sie daran, dass Sie immer eine freie Funktion im selben Namespace wie mindestens eines der Argumente schreiben sollten. Da es in Ihrem Fall verboten ist, Funktionen zu std hinzuzufügen, bedeutet dies, dass Sie Ihre Funktion in den Namensraum llvm hinzufügen. Die Tatsache, dass Sie das StringRef Argument mit llvm:: qualifizieren mussten, war ein totes Geschenk.

Die Regeln der Funktion Auflösung sind ziemlich komplex, aber als eine schnelle Skizze:

  • Namenssuche: sammelt eine Reihe von potenziellen Kandidaten
  • Überlastung Auflösung: nimmt der beste Kandidat unter den Potentialen
  • Spezialisierungsauflösung: Wenn der Kandidat eine Funktionsschablone ist, prüfen Sie, ob irgendeine Spezialisierung

Die Namensnachschlagephase ist, die wir behandeln ist hier relativ einfach. Kurz gesagt:

  • scannt die Namespaces des Arguments, dann ihre Eltern, ...bis er den globalen Bereich
  • geht dann erreicht durch den aktuellen Bereich Scannen, dann übergeordneter Rahmen, ..., bis es erreicht den globalen Bereich

Wahrscheinlich to allow shadowing (wie für jeden anderen Namen Lookup), der Nachschlag stoppt bei dem ersten Bereich, in dem es auf ein Match trifft, und ignoriert hochmütig jeden umgebenden Bereich.

Beachten Sie, dass using Direktiven (using ::operator<<; zum Beispiel) verwendet werden können, um einen Namen aus einem anderen Bereich einzuführen. Es ist jedoch lästig, da es den Kunden belastet, also bitte verlass dich nicht auf seine Verfügbarkeit als Entschuldigung für Schlamperei (was ich gesehen habe: x).


Beispiel shadowing: Druckt "Hello, World" ohne eine Mehrdeutigkeit Fehler zu erhöhen.

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

Beispiel interrupted search: ::hello::world ergab eine Funktion mit dem Namen print so wurde es ausgesucht, obwohl es überhaupt nicht übereinstimmen und ::hello::print wäre eine streng bessere Übereinstimmung gewesen sein.

#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; 
} 
Verwandte Themen