Warum muss ich std :: cout schreiben und nicht auch std :: <<

70

Warum muss ich schreiben std::coutund nicht auch std::<<in eine Codezeile wie diese:

#include <iostream>

int main() {
    std::cout << "Hello, world!";
    return 0;
}

coutkommt aus der stdBibliothek und wird <<normalerweise nicht zum Verschieben von Bits verwendet? Warum muss ich den Scope-Operator nicht ::auch vorher schreiben <<, da er auch mit einer anderen Bedeutung verwendet wird? Wie der Compiler weiß , dass nach std::cout, <<bedeutet eine andere Sache?

Francesco Bonizzi
quelle
22
Argumentabhängige Suche
RiaD
5
Dieses Verhalten ist auch als Koenig Lookup oder ADL
P0W
9
Wenn Sie <<das übrigens vollständig qualifizieren würden, müsste es so aussehen std::operator<<(std::cout, "Hello, world!");. Mit der C ++ - Syntax können Sie nicht schreiben std::cout std::<< "Hello, world!", um dasselbe zu bedeuten, da der Begriff der Namespaces nicht für die Operatoren selbst gilt, sondern für die Namen der Funktionen, die sie überladen, z. operator<<B.
Steve Jessop
9
+1 dafür. Es ist nicht etwas, das sich viele Leute selbst einfallen lassen.
Chris
7
Andere haben bereits die argumentabhängige Suche (auch bekannt als Koenig-Suche) erwähnt. Ich denke, es lohnt sich hinzuzufügen, dass dies genau der ursprüngliche Grund ist , warum Andrew Koenig überhaupt die argumentabhängige Suche erfunden hat. Es tritt an anderen Stellen auf, aber das Einfügen in (und das Extrahieren aus) iostreams war der Grund dafür.
Jerry Coffin

Antworten:

55

Zunächst betrachtet der Compiler die Typen links und rechts von <<. std::coutist vom Typ std::ostream, das String-Literal ist vom Typ Array von 15const char . Da die linke vom Klassentyp ist, wird nach einer Funktion mit dem Namen gesucht operator<<. Die Frage ist, wo wird es aussehen?

Die Suche nach diesem Namen operator<<ist eine sogenannte unqualifizierte Suche, da der Funktionsname nicht wie qualifiziert ist std::operator<<. Die unqualifizierte Suche nach Funktionsnamen ruft eine argumentabhängige Suche auf. Die argumentabhängige Suche sucht in den Klassen und Namespaces, die den Argumenttypen zugeordnet sind.

Wenn Sie einschließen <iostream>, eine freie Funktion der Signatur

template<typename traits>
std::basic_ostream<char, traits>& operator<<(std::basic_ostream<char, traits>&,
                                             const char*);

wurde im Namespace deklariert std. Dieser Namespace ist dem Typ von zugeordnet std::cout, daher wird diese Funktion gefunden.

std::ostreamist nur ein typedef für std::basic_ostream<char, std::char_traits<char>>, und das Array von 15const char kann implizit in a konvertiert werden char const*(zeigt auf das erste Element des Arrays). Daher kann diese Funktion mit den beiden Argumenttypen aufgerufen werden.

Es gibt andere Überladungen von operator<<, aber die oben erwähnte Funktion passt am besten zu den Argumenttypen und der in diesem Fall ausgewählten.


Ein einfaches Beispiel für eine argumentabhängige Suche:

namespace my_namespace
{
    struct X {};

    void find_me(X) {}
}

int main()
{
    my_namespace::X x;
    find_me(x);       // finds my_namespace::find_me because of the argument type
}

NB Da diese Funktion ein Operator ist, ist die eigentliche Suche etwas komplexer. Es wird über eine qualifizierte Suche im Bereich des ersten Arguments (wenn es sich um einen Klassentyp handelt) nachgeschlagen, dh als Elementfunktion. Zusätzlich wird eine unqualifizierte Suche durchgeführt, wobei jedoch alle Elementfunktionen ignoriert werden. Das Ergebnis ist etwas anders, da die unqualifizierte Suche tatsächlich einer zweistufigen Prozedur ähnelt, bei der die argumentabhängige Suche der zweite Schritt ist. Wenn der erste Schritt eine Elementfunktion gefunden hat , wird der zweite Schritt nicht durchgeführt, dh Argument abhängige lookup wird nicht verwendet.

Vergleichen Sie:

namespace my_namespace
{
    struct X
    {
        void find_me(X, int) {}
        void search();
    };
    void find_me(X, double) {}

    void X::search() {
        find_me(*this, 2.5); // only finds X::find_me(int)
        // pure unqualified lookup (1st step) finds the member function
        // argument-dependent lookup is not performed
    }
}

zu:

namespace my_namespace
{
    struct X
    {
        void operator<<(int) {}
        void search();
    };
    void operator<<(X, double) {}

    void X::search() {
        *this << 2.5; // find both because both steps are always performed
        // and overload resolution selects the free function
    }
}
dyp
quelle
6
Als Informatiker ist dies die richtige Antwort. Als Ingenieur habe ich eine kürzere Antwort: " Von Natur aus. Namespaces erfordern eine begrenzte zusätzliche Qualifikation. Genug, um zu unterscheiden, aber nicht mehr."
MSalters
1
Übrigens kann ADL deaktiviert werden, indem der Funktionsname in Klammern gesetzt wird : (find_me)(x). Ich weiß nicht, ob / wie das für die Operatorsyntax funktioniert.
Matthias
@ Matthias Interessant. Für Operatoren können Sie entweder die unqualifizierte Suche stoppen, z. B. über eine using-Deklaration (Mitgliedsfunktionen werden noch gefunden), oder Sie können die explizite Syntax verwenden (operator<<)(std::cout, "Hello, world!"). Live Beispiel
Dyp
9

Im std::cout << "Hello, world!"; //calls std:::operator <<

Dies wird mit der argumentabhängigen Namenssuche (ADL, auch bekannt als Koenig Lookup ) erreicht.

Wir haben zwar nur ein stdQualifikationsmerkmal, aber es gibt zwei Dinge, die sich aus dem stdNamespace ergeben

  • cout
  • <<

Ohne ADL (Koenig Lookup)

std::cout std:: << "Hello World" ;//this won't compile

Um es zu kompilieren, müssen wir es hässlichere Form verwenden

std::operator<<(std::cout, "Hello, world!");

Um solch eine hässliche Syntax zu vermeiden, müssen wir Koenig Lookup schätzen :)

P0W
quelle
3

Der Compiler erkennt, dass die Argumente für << ein std :: ostream-Objekt und eine Zeichenfolge sind, und kann daher die richtige Operator << -Definition basierend darauf finden.

Sie können sich die Argumenttypen eines Operators (oder wirklich jeder Funktion) als Teil seines Namens vorstellen.

Alec
quelle