Tengo curiosidad por saber cómo nullptr
funciona. Las normas N4659 y N4849 dicen:
- tiene que tener tipo
std::nullptr_t
; - no puedes tomar su dirección;
- se puede convertir directamente a un puntero y puntero a miembro;
sizeof(std::nullptr_t) == sizeof(void*)
;- su conversión a
bool
esfalse
; - 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_t
en 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){};
};
c++
c++17
nullptr
null-pointer
Fullfungo
fuente
fuente
nullptr_t
Es un tipo fundamental. ¿Cómo seint
implementa?#ifdef _LIBCPP_HAS_NO_NULLPTR
. Esto parece una solución alternativa de mejor esfuerzo para cuando el compilador no proporcionanullptr
.nullptr_t
es un tipo fundamental. Implementarlo como un tipo de clase no hace una implementación conforme. Ver el comentario de Chris.is_class
yis_null_pointer
ambos 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.Respuestas:
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_t
usando las reglas del lenguaje C ++. La conversión de una constante de tipo de puntero nulostd::nullptr_t
a 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_t
como 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 adecuadostd::nullptr_t
. Puede ver esto por el hecho de que#define
esnullptr
, mientras que C ++ 11 dice quenullptr
es una palabra clave , no una macro.C ++ no se puede implementar
std::nullptr_t
, al igual que C ++ no se puede implementarint
ovoid*
. Solo la implementación puede implementar esas cosas. Esto es lo que lo convierte en un "tipo fundamental"; Es parte del lenguaje .Ahi esta hay conversión implícita de un puntero nulo constante a tipos integrales. Hay una conversión de
0
a un tipo integral, pero eso es porque es el entero cero literal, que es ... un entero.nullptr_t
se puede convertir a un tipo entero (víareinterpret_cast
), pero solo se puede convertir implícitamente en punteros y abool
.fuente
std::nullptr_t
. Del mismo modo que no puede escribir un tipo que sea exactamente equivalente al comportamiento requeridoint
. Puedes acercarte, pero aún habrá diferencias significativas. Y no estoy hablando de detectores de rasgos comois_class
ese 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.nullptr_t
", hablas demasiado ampliamente. Y decir "solo la implementación puede implementarlo" solo confunde las cosas. Lo que quieres decir es quenullptr_t
no se puede implementar " en la biblioteca de C ++ porque es parte del lenguaje base.std::nullptr_t
se requiere. Al igual que C ++, el lenguaje no puede implementar un tipo que haga todo lo queint
se requiere hacer.