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){};
};
Fullfungo
la source
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 .

Nicol Bolas
la source
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