Comment fonctionne l'implémentation C ++ nullptr?


13

Je suis curieux de savoir comment ça nullptrmarche. Les normes N4659 et N4849 stipulent:

  1. il doit avoir du type std::nullptr_t;
  2. vous ne pouvez pas prendre son adresse;
  3. il peut être directement converti en pointeur et pointeur en membre;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. sa conversion en boolest false;
  6. sa valeur peut être convertie en type intégral à l'identique (void*)0, mais pas à l'envers;

C'est donc fondamentalement une constante avec la même signification que (void*)0, mais elle a un type différent. J'ai trouvé l'implémentation de std::nullptr_tsur mon appareil et c'est comme suit.

#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

Je suis cependant plus intéressé par la première partie. Il semble satisfaire les points 1-5, mais je n'ai aucune idée pourquoi il a une sous-classe __nat et tout ce qui s'y rapporte. Je voudrais également savoir pourquoi il échoue sur les conversions intégrales.

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){};
};

2
nullptr_test un type fondamental. Comment est-il intmis en œuvre?
LF

9
Remarque #ifdef _LIBCPP_HAS_NO_NULLPTR. Cela semble être une solution de contournement lorsque le compilateur ne fournit pas nullptr.
chris

5
@Fullfungo La norme dit que nullptr_tc'est un type fondamental. L'implémenter en tant que type de classe ne fait pas une implémentation conforme. Voir le commentaire de chris.
LF

1
@LF La norme exige-t-elle techniquement qu'un type fondamental ne soit pas un type classe?
eerorika

2
@eerorika: is_classet is_null_pointerne peut pas être vrai pour le même type. Une seule des fonctions de catégorie de type principal peut renvoyer true pour un type spécifique.
Nicol Bolas

Réponses:


20

Je suis curieux de savoir comment fonctionne nullptr.

Cela fonctionne de la manière la plus simple possible: par fiat . Cela fonctionne parce que la norme C ++ dit que cela fonctionne, et cela fonctionne comme il le fait parce que la norme C ++ dit que les implémentations doivent le faire fonctionner de cette façon.

Il est important de reconnaître qu'il est impossible d'implémenter en std::nullptr_tutilisant les règles du langage C ++. La conversion d'une constante de pointeur null de type std::nullptr_ten pointeur n'est pas une conversion définie par l'utilisateur. Cela signifie que vous pouvez passer d'une constante de pointeur nul à un pointeur, puis passer par une conversion définie par l'utilisateur vers un autre type, le tout dans une seule séquence de conversion implicite.

Ce n'est pas possible si vous implémentez en nullptr_ttant que classe. Les opérateurs de conversion représentent des conversions définies par l'utilisateur, et les règles de séquence de conversion implicites de C ++ ne permettent pas plus d'une conversion définie par l'utilisateur dans une telle séquence.

Le code que vous avez publié est donc une bonne approximation std::nullptr_t, mais ce n'est rien de plus que cela. Ce n'est pas une implémentation légitime du type. Ce fut probablement d'une ancienne version du compilateur ( à gauche pour des raisons de compatibilité ascendante) avant que le compilateur fourni un soutien approprié pour std::nullptr_t. Vous pouvez voir cela par le fait que c'est #defines nullptr, tandis que C ++ 11 dit que nullptrc'est un mot - clé , pas une macro.

C ++ ne peut pas implémenter std::nullptr_t, tout comme C ++ ne peut pas implémenterint ou void*. Seule l'implémentation peut implémenter ces choses. C'est ce qui en fait un "type fondamental"; cela fait partie de la langue .


sa valeur peut être convertie en type intégral de manière identique à (void *) 0, mais pas en arrière;

Il y a pas de conversion implicite d'une constante de pointeur nul en types intégraux. Il y a une conversion de 0à un type intégral, mais c'est parce que c'est le zéro littéral entier, qui est ... un entier.

nullptr_tpeut être jeté à un type entier (via reinterpret_cast), mais elle ne peut être converti implicitement à des pointeurs etbool .


4
@Wyck: " fiat "
Nicol Bolas

Que signifie "il est impossible d'implémenter std :: nullptr_t en utilisant les règles du langage C ++"? Cela signifie-t-il qu'un compilateur C ++ ne peut pas être complètement écrit en C ++ lui-même (je suppose que non)?
nordiste le

3
@ Northerner: Je veux dire que vous ne pouvez pas écrire un type exactement équivalent au comportement requis std::nullptr_t. Tout comme vous ne pouvez pas écrire un type exactement équivalent au comportement requis de int. Vous pouvez vous rapprocher, mais il y aura toujours des différences importantes. Et je ne parle pas de détecteurs de traits comme ceux-ci is_classqui montrent que votre type est défini par l'utilisateur. Il y a des choses sur le comportement requis des types fondamentaux que vous ne pouvez tout simplement pas copier en utilisant les règles de la langue.
Nicol Bolas

1
Juste un petit mot de formulation. Lorsque vous dites "C ++ ne peut pas être implémenté nullptr_t", vous parlez trop largement. Et dire "seule l'implémentation peut l'implémenter" ne fait que confondre les choses. Ce que vous voulez dire, c'est que nullptr_tcela ne peut pas être implémenté " dans la bibliothèque C ++ car il fait partie du langage de base.
Spencer

1
@Spencer: Non, je voulais dire exactement ce que j'ai dit: le langage C ++ ne peut pas être utilisé pour implémenter un type qui fait tout ce qui std::nullptr_test nécessaire pour le faire. Tout comme C ++, le langage ne peut pas implémenter un type qui fait tout ce qui intest nécessaire pour le faire.
Nicol Bolas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.