Como a implementação do c ++ nullptr funciona?

13

Estou curioso para saber como nullptrfunciona. Os padrões N4659 e N4849 dizem:

  1. tem que ter tipo std::nullptr_t;
  2. você não pode pegar o endereço dele;
  3. pode ser convertido diretamente em um ponteiro e ponteiro em membro;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. sua conversão para boolé false;
  6. seu valor pode ser convertido para o tipo integral de forma idêntica (void*)0, mas não para trás;

Portanto, é basicamente uma constante com o mesmo significado que (void*)0, mas tem um tipo diferente. Eu encontrei a implementação std::nullptr_tno meu dispositivo e é a seguinte.

#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

Estou mais interessado na primeira parte. Parece satisfazer os pontos 1 a 5, mas não tenho idéia do porquê de ter uma subclasse __nat e tudo relacionado a ela. Eu também gostaria de saber por que ele falha em conversões integrais.

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
fonte
2
nullptr_té um tipo fundamental. Como é intimplementado?
LF
9
Nota #ifdef _LIBCPP_HAS_NO_NULLPTR. Isso parece ser uma solução alternativa de melhor esforço para quando o compilador não fornece nullptr.
chris
5
@ Fullfungo O padrão diz que nullptr_té um tipo fundamental. Implementá-lo como um tipo de classe não faz uma implementação em conformidade. Veja o comentário de chris.
LF
11
@LF O padrão exige tecnicamente que um tipo fundamental não seja do tipo classe?
eerorika 18/04
2
@eerorika: is_classe is_null_pointerambos não podem ser verdadeiros para o mesmo tipo. Somente uma das funções da categoria de tipo principal pode retornar true para um tipo específico.
Nicol Bolas 18/04

Respostas:

20

Estou curioso para saber como o nullptr funciona.

Funciona da maneira mais simples possível: por decreto . Funciona porque o padrão C ++ diz que funciona e funciona da mesma maneira porque o padrão C ++ diz que as implementações devem fazê-lo funcionar dessa maneira.

É importante reconhecer que é impossível implementar std::nullptr_tusando as regras da linguagem C ++. A conversão de uma constante de ponteiro nulo do tipo std::nullptr_tpara um ponteiro não é uma conversão definida pelo usuário. Isso significa que você pode passar de uma constante de ponteiro nula para um ponteiro e, em seguida, através de uma conversão definida pelo usuário para outro tipo, tudo em uma única sequência de conversão implícita.

Isso não é possível se você implementar nullptr_tcomo uma classe. Os operadores de conversão representam conversões definidas pelo usuário, e as regras implícitas da sequência de conversão do C ++ não permitem mais de uma conversão definida pelo usuário nessa sequência.

Portanto, o código que você postou é uma boa aproximação std::nullptr_t, mas não é nada além disso. Não é uma implementação legítima do tipo. Provavelmente, era de uma versão mais antiga do compilador (deixada por motivos de compatibilidade com versões anteriores) antes de o compilador fornecer suporte adequado std::nullptr_t. Você pode ver isso pelo fato de que ele #defineé nullptr, ao mesmo tempo C ++ 11 diz que nullptré uma palavra-chave , não um macro.

C ++ não pode implementar std::nullptr_t, assim como C ++ não pode implementar intou void*. Somente a implementação pode implementar essas coisas. É isso que o torna um "tipo fundamental"; faz parte da linguagem .


seu valor pode ser convertido no tipo integral de forma idêntica para (vazio *) 0, mas não para trás;

Não há conversão implícita de uma constante de ponteiro nula para tipos integrais. Há uma conversão de 0para um tipo integral, mas é porque é o número inteiro literal zero, que é ... um número inteiro.

nullptr_tpode ser convertido em um tipo inteiro (via reinterpret_cast), mas só pode ser convertido implicitamente em ponteiros e em bool.

Nicol Bolas
fonte
4
@Wyck: " fiat "
Nicol Bolas
O que significa "é impossível implementar std :: nullptr_t usando as regras da linguagem C ++"? Isso significa que um compilador C ++ não pode ser completamente escrito no próprio C ++ (acho que não)?
northerner
3
@ northerner: quero dizer que você não pode escrever um tipo que seja exatamente equivalente ao comportamento exigido std::nullptr_t. Assim como você não pode escrever um tipo que seja exatamente equivalente ao comportamento exigido de int. Você pode se aproximar, mas ainda haverá diferenças significativas. E não estou falando de detectores de características como is_classessa que expõem que seu tipo é definido pelo usuário. Há coisas sobre o comportamento exigido de tipos fundamentais que você simplesmente não pode copiar usando as regras do idioma.
Nicol Bolas 18/04
11
Apenas uma queixa de palavras. Quando você diz "C ++ não pode implementar nullptr_t", você fala muito amplamente. E dizer "apenas a implementação pode implementá-lo" apenas confunde as coisas. O que você quer dizer é que nullptr_tnão pode ser implementado " na biblioteca C ++ porque faz parte da linguagem base.
Spencer
11
@ Spencer: Não, eu quis dizer exatamente o que disse: C ++ a linguagem não pode ser usada para implementar um tipo que faz tudo o que std::nullptr_té necessário. Assim como C ++, a linguagem não pode implementar um tipo que faz tudo o que inté necessário.
Nicol Bolas