Как работает реализация C++ nullptr?

Мне любопытно узнать, как работает nullptr. Стандарты N4659 и N4849 говорят:

  1. он должен иметь тип std::nullptr_t;
  2. вы не можете взять его адрес;
  3. его можно напрямую преобразовать в указатель и указатель на элемент;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. его преобразование в bool равно false;
  6. его значение может быть преобразовано в целочисленный тип аналогично (void*)0, но не наоборот;

Так что в основном это константа с тем же значением, что и (void*)0, но другого типа. Я нашел реализацию std::nullptr_t на своем устройстве, и она выглядит следующим образом.

#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

Хотя меня больше интересует первая часть. Кажется, он удовлетворяет пунктам 1-5, но я понятия не имею, почему у него есть подкласс __nat и все, что с ним связано. Я также хотел бы знать, почему он терпит неудачу при интегральных преобразованиях.

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

person Fullfungo    schedule 18.04.2020    source источник
comment
nullptr_t является фундаментальным типом. Как реализован int?   -  person L. F.    schedule 18.04.2020
comment
Примечание #ifdef _LIBCPP_HAS_NO_NULLPTR. Это похоже на лучший обходной путь, когда компилятор не предоставляет nullptr.   -  person chris    schedule 18.04.2020
comment
@ Л.Ф. Подобно тому, как реализован float   -  person Ardent Coder    schedule 18.04.2020
comment
Не гарантируется, что nullptr будет реализован, он может иметь специальное определение, такое как в коде.   -  person Fullfungo    schedule 18.04.2020
comment
@Fullfungo Стандарт говорит, что nullptr_t является фундаментальным типом. Реализация его как типа класса не дает соответствующей реализации. Смотрите комментарий Криса.   -  person L. F.    schedule 18.04.2020
comment
Связано: безопасная идиома bool Is идиома safe-bool устарела в C++11?   -  person L. F.    schedule 18.04.2020
comment
@Л. Ф. Хорошо, понятно. Я все еще хочу знать, что делает __nat и почему он здесь, может быть, это поможет мне в моем собственном коде.   -  person Fullfungo    schedule 18.04.2020
comment
@ Л.Ф. Требует ли стандарт технически, чтобы фундаментальный тип не был типом класса?   -  person eerorika    schedule 18.04.2020
comment
@Fullfungo в основном используется для безопасной логической идиомы до C++11. Простой operator bool позволит преобразовать его, скажем, в int или типы классов, конструируемые из bool.   -  person L. F.    schedule 18.04.2020
comment
@eerorika Ничего себе, раньше я всегда предполагал, что это правда, но, кажется, самое близкое, что я могу найти, это примечание, в котором говорится, что существует два типа типов: основные типы и составные типы...   -  person L. F.    schedule 18.04.2020
comment
@eerorika: is_class и is_null_pointer не могут быть верны для одного и того же типа. Только одна из функций категории основного типа может возвращать значение true для определенного тип.   -  person Nicol Bolas    schedule 18.04.2020
comment
@NicolBolas Простыми словами: примитивные типы и пользовательские типы являются взаимоисключающими классификациями.   -  person Ardent Coder    schedule 18.04.2020
comment
@NicolBolas, может ли реализация технически использовать особый случай, чтобы они по-прежнему возвращали правильные результаты?   -  person chris    schedule 18.04.2020
comment
@chris: эта версия is_class и is_null_pointer может лгать, создавая явное исключение для этого типа std::nullptr_t, возвращая неверные значения.   -  person Nicol Bolas    schedule 18.04.2020
comment
Если это поможет, ключевое слово nullptr очень похоже на ключевые слова true и false: все они являются литералами, представляющими значения для типа, у которого изначально очень мало значений. Есть только два значения bool и только одно значение std::nullptr_t.   -  person aschepler    schedule 18.04.2020
comment
@chris Реализация может использовать любые читы, которые ей нравятся, чтобы поведение соответствующих программ действовало как воображаемая виртуальная машина C ++ в соответствии с правилом «как если бы» (timsong-cpp.github.io/cppwp/intro.abstract#1 и сноску).   -  person aschepler    schedule 18.04.2020


Ответы (1)


Мне любопытно узнать, как работает nullptr.

Это работает самым простым способом: fiat. Он работает, потому что стандарт C++ говорит, что он работает, и работает так, как он работает, потому что стандарт C++ говорит, что реализации должны заставлять его работать таким образом.

Важно понимать, что невозможно реализовать std::nullptr_t, используя правила языка C++. Преобразование константы нулевого указателя типа std::nullptr_t в указатель не является преобразованием, определяемым пользователем. Это означает, что вы можете перейти от константы нулевого указателя к указателю, а затем через определяемое пользователем преобразование в какой-либо другой тип, и все это в одной последовательности неявного преобразования.

Это невозможно, если вы реализуете nullptr_t как класс. Операторы преобразования представляют определяемые пользователем преобразования, а правила последовательности неявных преобразований C++ не допускают более одного определяемого пользователем преобразования в такой последовательности.

Таким образом, код, который вы разместили, является хорошим приближением к std::nullptr_t, но это не более того. Это незаконная реализация типа. Вероятно, это была старая версия компилятора (оставленная по соображениям обратной совместимости) до того, как компилятор обеспечил надлежащую поддержку std::nullptr_t. Вы можете видеть это по тому факту, что это #defines nullptr, в то время как C++11 говорит, что nullptr является ключевым словом, а не макросом.

C++ не может реализовать std::nullptr_t, так же как C++ не может реализовать int или void*. Только реализация может реализовать эти вещи. Именно это делает его «фундаментальным типом»; это часть языка.


его значение может быть преобразовано в целочисленный тип идентично (void*)0, но не наоборот;

Не существует неявного преобразования констант нулевого указателя в целочисленные типы. Существует преобразование из 0 в целочисленный тип, но это потому, что это целочисленный литерал ноль, который является... целым числом.

nullptr_t можно привести к целочисленному типу (через reinterpret_cast), но его можно только неявно преобразовать в указатели и в bool.

person Nicol Bolas    schedule 18.04.2020
comment
Что значит невозможно реализовать std::nullptr_t по правилам языка C++? Означает ли это, что компилятор С++ не может быть полностью написан на самом С++ (я полагаю, что нет)? - person northerner; 18.04.2020
comment
@northerner: я имею в виду, что вы не можете написать тип, который точно эквивалентен поведению, требуемому от std::nullptr_t. Точно так же, как вы не можете написать тип, полностью эквивалентный поведению, требуемому от int. Можно приблизиться, но существенные различия все же будут. И я не говорю о детекторах признаков, таких как is_class, которые показывают, что ваш тип определяется пользователем. Есть вещи в требуемом поведении фундаментальных типов, которые вы просто не можете скопировать, используя правила языка. - person Nicol Bolas; 18.04.2020
comment
Просто придирка к формулировкам. Когда вы говорите, что C++ не может реализовать nullptr_t, вы говорите слишком широко. И говорить, что только реализация может реализовать это, только смущает. Вы имеете в виду, что nullptr_t нельзя реализовать в библиотеке C++, потому что это часть базового языка. - person Spencer; 18.04.2020
comment
@Spencer: Нет, я имел в виду именно то, что сказал: язык C++ нельзя использовать для реализации типа, который делает все, что требуется для std::nullptr_t. Как и C++, язык не может реализовать тип, который делает все, что требуется int. - person Nicol Bolas; 18.04.2020
comment
Вы не сказали, что С++ нельзя использовать для реализации, вы сказали, что С++ не может реализовать. Если вы отредактируете свой ответ, чтобы сказать первое, а не второе, я соглашусь с этим. - person Spencer; 18.04.2020
comment
@Spencer: Из первого предложения, второй абзац: невозможно реализовать std::nullptr_t, используя правила языка C++. Контекст имеет значение. - person Nicol Bolas; 18.04.2020
comment
И я не противоречу этому. Я просто хочу, чтобы вы лучше сформулировали это в своем ответе, потому что слова имеют значение. - person Spencer; 18.04.2020