¿Cómo funciona la implementación de c ++ nullptr?

13

Tengo curiosidad por saber cómo nullptrfunciona. Las normas N4659 y N4849 dicen:

  1. tiene que tener tipo std::nullptr_t;
  2. no puedes tomar su dirección;
  3. se puede convertir directamente a un puntero y puntero a miembro;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. su conversión a booles false;
  6. su valor se puede convertir a tipo integral de manera idéntica (void*)0, pero no al revés;

Entonces es básicamente una constante con el mismo significado que (void*)0, pero tiene un tipo diferente. He encontrado la implementación de std::nullptr_ten mi dispositivo y es la siguiente.

#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

Sin embargo, estoy más interesado en la primera parte. Parece satisfacer los puntos 1-5, pero no tengo idea de por qué tiene una subclase __nat y todo lo relacionado con ella. También me gustaría saber por qué falla en las conversiones integrales.

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
fuente
2
nullptr_tEs un tipo fundamental. ¿Cómo se intimplementa?
LF
99
Nota #ifdef _LIBCPP_HAS_NO_NULLPTR. Esto parece una solución alternativa de mejor esfuerzo para cuando el compilador no proporciona nullptr.
Chris
55
@Fullfungo El estándar dice que nullptr_tes un tipo fundamental. Implementarlo como un tipo de clase no hace una implementación conforme. Ver el comentario de Chris.
LF
1
@LF ¿El estándar requiere técnicamente que un tipo fundamental no sea un tipo de clase?
Eerorika
2
@eerorika: is_classy is_null_pointerambos no pueden ser ciertos para el mismo tipo. Solo una de las funciones de categoría de tipo primario puede devolver verdadero para un tipo específico.
Nicol Bolas

Respuestas:

20

Tengo curiosidad por saber cómo funciona nullptr.

Funciona de la manera más simple posible: por fiat . Funciona porque el estándar C ++ dice que funciona, y funciona de la manera en que lo hace porque el estándar C ++ dice que las implementaciones deben hacerlo funcionar de esa manera.

Es importante reconocer que es imposible implementar std::nullptr_tusando las reglas del lenguaje C ++. La conversión de una constante de tipo de puntero nulo std::nullptr_ta un puntero no es una conversión definida por el usuario. Eso significa que puede pasar de una constante de puntero nulo a un puntero, luego a través de una conversión definida por el usuario a otro tipo, todo en una sola secuencia de conversión implícita.

Eso no es posible si se implementa nullptr_tcomo una clase. Los operadores de conversión representan conversiones definidas por el usuario, y las reglas de secuencia de conversión implícita de C ++ no permiten más de una conversión definida por el usuario en dicha secuencia.

Entonces, el código que publicaste es una buena aproximación std::nullptr_t, pero no es más que eso. No es una implementación legítima del tipo. Esto probablemente fue de una versión anterior del compilador (dejado por razones de compatibilidad con versiones anteriores) antes de que el compilador proporcionara el soporte adecuado std::nullptr_t. Puede ver esto por el hecho de que #definees nullptr, mientras que C ++ 11 dice que nullptres una palabra clave , no una macro.

C ++ no se puede implementar std::nullptr_t, al igual que C ++ no se puede implementarint o void*. Solo la implementación puede implementar esas cosas. Esto es lo que lo convierte en un "tipo fundamental"; Es parte del lenguaje .


su valor se puede convertir a tipo integral de manera idéntica a (void *) 0, pero no al revés;

Ahi esta hay conversión implícita de un puntero nulo constante a tipos integrales. Hay una conversión de 0a un tipo integral, pero eso es porque es el entero cero literal, que es ... un entero.

nullptr_tse puede convertir a un tipo entero (vía reinterpret_cast), pero solo se puede convertir implícitamente en punteros y a bool.

Nicol Bolas
fuente
44
@Wyck: " fiat "
Nicol Bolas
¿Qué significa "es imposible implementar std :: nullptr_t usando las reglas del lenguaje C ++"? ¿Significa que un compilador de C ++ no se puede escribir completamente en C ++ en sí mismo (supongo que no)?
norteño
3
@northerner: quiero decir que no puedes escribir un tipo que sea exactamente equivalente al comportamiento requerido std::nullptr_t. Del mismo modo que no puede escribir un tipo que sea exactamente equivalente al comportamiento requerido int. Puedes acercarte, pero aún habrá diferencias significativas. Y no estoy hablando de detectores de rasgos como is_classese exponen que su tipo está definido por el usuario. Hay cosas sobre el comportamiento requerido de los tipos fundamentales que simplemente no puede copiar utilizando las reglas del lenguaje.
Nicol Bolas
1
Solo una objeción de redacción. Cuando dices "C ++ no se puede implementar nullptr_t", hablas demasiado ampliamente. Y decir "solo la implementación puede implementarlo" solo confunde las cosas. Lo que quieres decir es que nullptr_tno se puede implementar " en la biblioteca de C ++ porque es parte del lenguaje base.
Spencer
1
@Spencer: No, quise decir exactamente lo que dije: C ++, el lenguaje no se puede usar para implementar un tipo que haga todo lo que std::nullptr_tse requiere. Al igual que C ++, el lenguaje no puede implementar un tipo que haga todo lo que intse requiere hacer.
Nicol Bolas