Wie funktioniert die Implementierung von c ++ nullptr?

13

Ich bin gespannt, wie nullptrdas geht. Die Standards N4659 und N4849 sagen:

  1. es muss Typ haben std::nullptr_t;
  2. Sie können seine Adresse nicht nehmen;
  3. Es kann direkt in einen Zeiger und einen Zeiger auf ein Mitglied konvertiert werden.
  4. sizeof(std::nullptr_t) == sizeof(void*);;
  5. seine Umwandlung in boolist false;
  6. sein Wert kann identisch (void*)0, aber nicht rückwärts in einen ganzzahligen Typ umgewandelt werden ;

Es ist also im Grunde eine Konstante mit der gleichen Bedeutung wie (void*)0, aber es hat einen anderen Typ. Ich habe die Implementierung std::nullptr_tauf meinem Gerät gefunden und es ist wie folgt.

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

Der erste Teil interessiert mich allerdings mehr. Es scheint die Punkte 1-5 zu erfüllen, aber ich habe keine Ahnung, warum es eine Unterklasse __nat und alles, was damit zu tun hat, hat. Ich würde auch gerne wissen, warum es bei integralen Konvertierungen fehlschlägt.

struct nullptr_t2{
    void* __lx;
    struct __nat {int __for_bool_;};
     constexpr nullptr_t2() : __lx(0) {}
     constexpr nullptr_t2(int __nat::*) : __lx(0) {}
     constexpr operator int __nat::*() const {return 0;}
    template <class _Tp>
         constexpr
        operator _Tp* () const {return 0;}
    template <class _Tp, class _Up>
        operator _Tp _Up::* () const {return 0;}
    friend  constexpr bool operator==(nullptr_t2, nullptr_t2) {return true;}
    friend  constexpr bool operator!=(nullptr_t2, nullptr_t2) {return false;}
};
inline constexpr nullptr_t2 __get_nullptr_t2() {return nullptr_t2(0);}
#define nullptr2 __get_nullptr_t2()

int main(){
    long l  = reinterpret_cast<long>(nullptr);
    long l2 = reinterpret_cast<long>(nullptr2); // error: invalid type conversion
    bool b  = nullptr; // warning: implicit conversion
                       // edditor error: a value of type "std::nullptr_t" cannot be used to initialize an entity of type "bool"
    bool b2 = nullptr2;
    if (nullptr){}; // warning: implicit conversion
    if (nullptr2){};
};
Fullfungo
quelle
2
nullptr_tist ein grundlegender Typ. Wie wird intumgesetzt?
LF
9
Hinweis #ifdef _LIBCPP_HAS_NO_NULLPTR. Dies scheint eine bestmögliche Problemumgehung zu sein, wenn der Compiler keine Bereitstellung bietet nullptr.
Chris
5
@Fullfungo Der Standard sagt, dass dies nullptr_tein grundlegender Typ ist. Die Implementierung als Klassentyp führt nicht zu einer konformen Implementierung. Siehe Chris 'Kommentar.
LF
1
@LF Erfordert der Standard technisch, dass ein grundlegender Typ kein Klassentyp ist?
Eerorika
2
@eerorika: is_classund is_null_pointerkann nicht beide für den gleichen Typ wahr sein. Nur eine der Funktionen der primären Typkategorie kann für einen bestimmten Typ true zurückgeben.
Nicol Bolas

Antworten:

20

Ich bin gespannt, wie nullptr funktioniert.

Es funktioniert auf einfachste Weise: per Fiat . Es funktioniert, weil der C ++ - Standard sagt, dass es funktioniert, und es funktioniert so, wie es funktioniert, weil der C ++ - Standard sagt, dass Implementierungen dafür sorgen müssen, dass es auf diese Weise funktioniert .

Es ist wichtig zu erkennen, dass es unmöglich ist , std::nullptr_tmit den Regeln der C ++ - Sprache zu implementieren . Die Konvertierung von einer Nullzeigerkonstante vom Typ std::nullptr_tin einen Zeiger ist keine benutzerdefinierte Konvertierung. Das bedeutet, dass Sie von einer Nullzeigerkonstante zu einem Zeiger und dann durch eine benutzerdefinierte Konvertierung zu einem anderen Typ in einer einzigen impliziten Konvertierungssequenz wechseln können.

Dies ist nicht möglich, wenn Sie nullptr_tals Klasse implementieren . Konvertierungsoperatoren stellen benutzerdefinierte Konvertierungen dar, und die impliziten Konvertierungssequenzregeln von C ++ erlauben nicht mehr als eine benutzerdefinierte Konvertierung in einer solchen Sequenz.

Der Code, den Sie gepostet haben, ist also eine nette Annäherung an std::nullptr_t, aber es ist nichts weiter als das. Es ist keine legitime Implementierung dieses Typs. Dies war wahrscheinlich eine ältere Version des Compilers (aus Gründen der Abwärtskompatibilität), bevor der Compiler die richtige Unterstützung für bereitstelltestd::nullptr_t . Sie können dies durch die Tatsache , dass es #defines nullptr, während C ++ 11 sagt , dass nullptrist ein Schlüsselwort , kein Makro.

C ++ kann nicht implementiert werden std::nullptr_t, genauso wie C ++ nicht implementieren kannint oder void*. Nur die Implementierung kann diese Dinge implementieren. Dies macht es zu einem "fundamentalen Typ"; Es ist ein Teil der Sprache .


sein Wert kann in einen ganzzahligen Typ umgewandelt werden, der identisch mit (void *) 0 ist, jedoch nicht rückwärts;

Es gibt keine implizite Konvertierung von einer Nullzeigerkonstante in integrale Typen. Es gibt eine Konvertierung von 0in einen ganzzahligen Typ, aber das liegt daran, dass es sich um das ganzzahlige Literal Null handelt, das ... eine Ganzzahl ist.

nullptr_tkann gegossen (über in einen Integer - Typ reinterpret_cast), aber es kann nur implizit auf Zeiger und konvertiert werden bool.

Nicol Bolas
quelle
4
@ Wyck: " fiat "
Nicol Bolas
Was bedeutet "es ist unmöglich, std :: nullptr_t nach den Regeln der C ++ - Sprache zu implementieren"? Bedeutet das, dass ein C ++ - Compiler nicht vollständig in C ++ selbst geschrieben werden kann (ich vermute nicht)?
Nordländer
3
@northerner: Ich meine, Sie können keinen Typ schreiben, der genau dem Verhalten entspricht, das von erforderlich ist std::nullptr_t. Genauso wie Sie keinen Typ schreiben können, der genau dem Verhalten entspricht, das von erforderlich ist int. Sie können nah dran sein, aber es wird immer noch signifikante Unterschiede geben. Und ich spreche nicht von solchen Merkmaldetektoren is_class, die zeigen, dass Ihr Typ benutzerdefiniert ist. Es gibt Dinge über das erforderliche Verhalten grundlegender Typen, die Sie mit den Regeln der Sprache einfach nicht kopieren können.
Nicol Bolas
1
Nur ein Wortlaut. Wenn Sie sagen "C ++ kann nicht implementieren nullptr_t", sprechen Sie zu weit. Und zu sagen "nur die Implementierung kann es implementieren" verwirrt nur die Sache. Was Sie damit meinen, ist, dass nullptr_tdies nicht in der C ++ - Bibliothek implementiert werden kann, da es Teil der Basissprache ist.
Spencer
1
@Spencer: Nein, ich habe genau das gemeint, was ich gesagt habe: C ++ Die Sprache kann nicht verwendet werden, um einen Typ zu implementieren, der alles tut, was std::nullptr_tdazu erforderlich ist. Genau wie C ++ kann die Sprache keinen Typ implementieren, der alles tut, was interforderlich ist.
Nicol Bolas