Где находится nullptr_t?

Немного предыстории.

Я довольно давно пишу игровой движок. Он разделен на несколько статических библиотек, таких как «utils», «rsbin» (система ресурсов), «window», которые затем связываются в один исполняемый файл.

Это кроссплатформенный движок, компилируемый для Windows и Android. Под Windows я компилирую его с помощью MinGW. Под Android с помощью CCTools, который является интерфейсом к нативному gcc.

Одним из базовых классов является utils::RefObject, который представляет концепцию, аналогичную IUnknown в Windows: он предоставляет счетчик ссылок для определения времени жизни и метод для запроса определенного интерфейса из указателя базового класса. Также есть template< typename T > utils::Ref, созданный специально для таких объектов. Он содержит std::atomic< utils::RefObject* > и автоматически обновляет счетчик ссылок своего объекта при построении, назначении и уничтожении аналогично std::shared_ptr. Это также позволяет неявно преобразовывать RefObjects разных типов с помощью их методов запроса. Однако неэффективно запрашивать объект для его собственного типа, поэтому utils::Ref перегружает большинство своих операторов, например. грамм. есть специальный конструктор utils::Ref< T >::Ref( T* ptr ), который просто увеличивает количество ссылок переданного объекта, и общий utils::Ref< T >::Ref( RefObject* ptr ), который запрашивает свой аргумент для экземпляра T и выдает исключение в случае неудачи (хотя не волнуйтесь, конечно, есть метод для мягкого приведения).

Но наличие только этих двух методов создает проблему: вы не можете явно инициализировать utils::Ref нулевым указателем, так как это неоднозначно; так что есть также utils::Ref< T >::Ref( nullptr_t ), чтобы предоставить способ сделать это.

Теперь мы подходим к проблеме. В заголовочном файле прототип пишется точно так же, как указано выше, без предшествующего std::. Обратите внимание, что я также не использую using namespace. Долгое время это работало.

Сейчас я работаю над графической системой. Он существовал и раньше, но был довольно рудиментарным, поэтому я даже не заметил, что ‹gl.h› на самом деле определяет только OpenGL 1.1, а для более новых версий нужно использовать ‹glext.h›. Теперь возникла необходимость использовать последний. Но в том числе и это сломало старый эталонный класс.

Судя по сообщениям об ошибках, у MinGW теперь есть проблемы с этим nullptr_t в прототипах. Я провел быстрый поиск в Интернете и обнаружил, что часто его называют std::nullptr_t. Хотя, не везде.

Подводя итог: у меня было nullptr_t без std:: или using namespace компиляции, пока я не включил ‹glext.h› перед заголовком.

Сайт cplusplus.com/reference, который я использовал до сих пор, предполагает, что глобальный ::nullptr_t как должно быть. С другой стороны, вики en.cppreference.com сообщает, что на самом деле это std::nullptr_t.

Быстрая тестовая программа, helloworld с void foo( int ) и void foo( nullptr_t ), не скомпилировалась, и теперь причина явно "error: 'nullptr_t' was not declared in this scope" с предложением использовать вместо нее std::nullptr_t.

Нетрудно будет добавить std:: там, где это необходимо; но этот случай оставил меня довольно любопытным.

cplusplus.com на самом деле лгал? => Ответил в кометах, да. Это неточный источник.

Тогда, если nullptr_t действительно находится в namespace std, почему utils::Ref компилируется? => С предложениями в комментариях, провел пару тестов и обнаружил, что ‹mutex›, включенный в какой-то другой заголовок, при размещении перед любым заголовком stddef определяет глобальный ::nullptr_t. Конечно, не идеальное поведение, но это не серьезная ошибка. Вероятно, в любом случае следует сообщить об этом разработчикам MinGW/GCC.

Почему включение ‹glext.h› ломает его? => Когда какой-либо заголовок stddef включен перед ‹mutex›, тип определяется в соответствии со стандартом как std::nullptr_t. ‹glext.h› включает в себя ‹windows.h›, который, в свою очередь, обязательно включает заголовок stddef, а также целый пакет других необходимых для WinAPI.

Вот источники, определяющие рассматриваемый класс:

(последние 2 включены и поэтому тоже могут повлиять)

Как было предложено в комментариях, я запустил g++ -E в тестовом случае, который скомпилировался, и нашел довольно интересный фрагмент в ‹stddef.h›:

#if defined(__cplusplus) && __cplusplus >= 201103L
#ifndef _GXX_NULLPTR_T
#define _GXX_NULLPTR_T
  typedef decltype(nullptr) nullptr_t;
#endif
#endif /* C++11.  */

Теперь, чтобы найти, где _GXX_NULLPTR_T определяется еще... быстрый GREP через файлы MinGW не нашел ничего, кроме этого stddef.h

Так что до сих пор остается загадкой, почему и как он отключается. Особенно, когда включение только ‹stddef.h› и ничего больше нигде не определяет nullptr_t, несмотря на чуток выше.


person Delfigamer    schedule 24.02.2015    source источник
comment
посмотрите на предварительно обработанный вывод, чтобы увидеть, где вводится ::nullptr_t.   -  person tenfour    schedule 24.02.2015
comment
FWIW, cplusplus.com известен своей неточностью.   -  person Angew is no longer proud of SO    schedule 24.02.2015
comment
Это относится к стандартным, ненулевым шансам, что этот исходный код сначала увидел другой компилятор, у которого было using ::std::nullptr_t; в заголовке. Как MSVC++.   -  person Hans Passant    schedule 24.02.2015
comment
@HansPassant Никогда не использовал другой компилятор в Windows для этого проекта.   -  person Delfigamer    schedule 24.02.2015
comment
@tenfour О, это помогло! Отредактированный ОП.   -  person Delfigamer    schedule 24.02.2015
comment
@Delfigamer Не похоже, что вы на самом деле включаете <stddef.h> где-либо и полагаетесь на какой-то другой заголовок, включающий его от вашего имени. Это ненадежно и не всегда работает. Если вы хотите что-то определенное в этом заголовке, включите этот заголовок.   -  person    schedule 24.02.2015
comment
Не хамите, а 90% вопроса надо?   -  person    schedule 24.02.2015
comment
@remyabel Это отвечает на возможные вопросы, например, почему ... почему ... и почему ... Нет, вы можете пропустить это прямо до жирной строки.   -  person Delfigamer    schedule 24.02.2015
comment
+1 за то, что потратил столько усилий, чтобы добавить контекст к вопросу. Всегда сложно решить, сколько контекста предоставить.   -  person BitTickler    schedule 24.02.2015
comment
Глупый вопрос от моего имени: почему он использует nullptr_t, а не nullptr напрямую? Это так плохо по какой-то причине? Другими словами, зачем вообще нужен тип nullptr? Не будет ли тип void* использоваться в этих случаях?   -  person BitTickler    schedule 24.02.2015
comment
@user2225104 user2225104 Как указано в OP, nullptr_t используется для перегрузки функций, принимая различные типы указателей, так как в противном случае их вызов с нулевым указателем потребовал бы его явного приведения к одному из принятых типов. Когда у вас есть void foo( int* ) и void foo( double* ), вызов foo( 0 ) неоднозначен: компилятор не знает, какую функцию вызывать. Мы можем привести к типу ноль, чтобы указать, какой foo нам нужен: foo( ( double* )0 ) будет преобразован в последний. Но C++11 дает нам более элегантный способ сделать это, введя особый случай void foo( nullptr_t )...   -  person Delfigamer    schedule 24.02.2015
comment
@user2225104 user2225104 ... а затем вызов foo( nullptr ), который однозначно разрешит свою собственную функцию. В utils::Ref, описанном в OP, этот синтаксис используется для явной инициализации ссылки с нулевым указателем и для ее назначения, когда нам больше не нужен объект, который он удерживал, и предоставление нового не имеет смысла.   -  person Delfigamer    schedule 24.02.2015
comment
@user2225104 Кроме того, это;)   -  person Delfigamer    schedule 24.02.2015
comment
Я начинаю понимать, что происходит. По сути, мне интересно, почему они продолжают разделять функциональность между языковыми и стандартными библиотеками. Если nullptr является внутренним значением, то std::nullptr_t должен быть nullptr_t и определяться языком (без заголовков). Тот же беспорядок, который они создали с лямбда-функциями и std::function. Только представьте, если бы true и false были языковыми значениями, а bool не существовало бы. Любой бы увидел, насколько это было бы больно.   -  person BitTickler    schedule 24.02.2015
comment
@user2225104 user2225104 У меня тоже была эта мысль о nullptr_t, так как я не вижу, чем он отличается от bool настолько, чтобы bool, false и true были встроенными, а тип встроенного nullptr - это какое-то объявление в заголовочном файле C. Логичнее было бы либо ввести письменный класс std::nullptr_t с nullptr_t const nullptr (что вполне возможно в C++), либо сделать их оба ключевыми словами. Что касается замыканий и, аналогично, списков инициализации, то они являются достаточно сложными понятиями, чтобы быть непредставимыми с элементарными типами, поэтому других способов их реализации не так много.   -  person Delfigamer    schedule 24.02.2015
comment
@delf bool — это зарезервированное слово в C++. Добавление новых зарезервированных слов может привести к нарушению обратной совместимости. Итак, за исключением веских причин, это не делается.   -  person Yakk - Adam Nevraumont    schedule 25.02.2015
comment
@user2225104 user2225104 std::function имеет очень мало общего с лямбда-выражениями. std::function — это общий функтор, который может содержать все, что может быть вызвано, и должен использовать стирание типа для достижения этого (с соответствующими затратами времени выполнения). Тип лямбда-выражения — это уникальный безымянный класс, тип замыкания.   -  person Angew is no longer proud of SO    schedule 25.02.2015
comment
@Angew Попробуйте получить удовольствие от лямбда-выражений, не используя std::function. Удачи с этим. И это само по себе является доказательством того, что они сделали разделение и неудачное разделение функциональности. Если бы они не были такими неуклюжими в отношении типов лямбда-выражений, вы могли бы использовать их без std::function.   -  person BitTickler    schedule 25.02.2015
comment
@user2225104 user2225104 Я использую лямбда-выражения много, и мне еще предстоит использовать их с std::function.   -  person Angew is no longer proud of SO    schedule 25.02.2015


Ответы (1)


Тип nullptr определен в пространстве имен ::std, поэтому правильным определением будет ::std::nullptr_t. Конечно, это означает, что на практике вы обычно пишете std::nullptr_t.

Цитирую С++ 11:

2.14.7/1:

Литерал указателя — это ключевое слово nullptr. Это значение типа std::nullptr_t.

18.2/9:

nullptr_t определяется следующим образом:

namespace std {
  typedef decltype(nullptr) nullptr_t;
}

Тип, для которого nullptr_t является синонимом, имеет характеристики, описанные в 3.9.1 и 4.10. [ Примечание. Хотя адрес nullptr взять нельзя, можно взять адрес другого объекта nullptr_t, который является lvalue. —конец примечания ]

<stddef.h> также входит в картину. 18.2 говорит о <cstddef>, так что это заголовок C++, где определено std::nullptr_t. Согласно D.5/2:

Каждый заголовок C, каждый из которых имеет имя формы name.h, ведет себя так, как если бы каждое имя, помещенное в пространство имен стандартной библиотеки с помощью соответствующего заголовка cname, помещается в глобальную область пространства имен.

Это означает, что включение <stddef.h> дает вам доступ к ::nullptr_t. Но поскольку это должен быть заголовок C, я бы не советовал полагаться на него в коде C++ (даже если он формально действителен).

person Angew is no longer proud of SO    schedule 24.02.2015
comment
ИМО, нет особого смысла говорить, что nullptr имеет тип nullptr_t, а не nullptr_t обозначает тип nullptr. nullptr_t — это просто определение типа. - person Columbo; 24.02.2015
comment
Учитывая, что в OP теперь упоминается <stddef.h>, вероятно, стоит упомянуть, что этот заголовок определяет ::nullptr_t точно так же, как <cstddef> определяет ::std::nullptr_t. - person ; 24.02.2015
comment
@Columbo Я считаю, что об этом есть DR, что определение в стандарте круглое. - person Angew is no longer proud of SO; 24.02.2015
comment
@Columbo Почему, дело в том, что nullptr указывает на аргумент nullptr_t в перегруженных функциях, и, похоже, здесь нет никакого преобразования. В коде C существует множество определений типов, но это не мешает нам сказать, что, например, 42 относится к типу WORD, а -19 и 0x12345 — нет. Кроме того, я расцениваю оба ваших предложения как обозначающие один и тот же факт разными словами: при вызове foo( nullptr ) самым первым кандидатом является void foo( std::nullptr_t ), а для foo( 12 ) - void foo( int ). - person Delfigamer; 24.02.2015
comment
@Delfigamer ... Я думаю, вы упустили суть. Очевидно, что оба они верны и обозначают одно и то же, но имеют разные последствия. - person Columbo; 24.02.2015
comment
@hvd О, отлично. Когда я включаю ‹cstddef›, компилятор требует std::nullptr_t. Когда я включаю ‹stddef.h›, нигде нет никакого типа. Включая ‹cstdio›, определенный std::nullptr_t. ‹stdio.h› не определяет ни то, ни другое. - person Delfigamer; 24.02.2015
comment
@Delfigamer <stddef.h> существует для вещей, перенесенных из C. Полагаться на чисто конструкцию C++, такую ​​​​как nullptr_t, объявленную в глобальной области действия <stddef.h>, вызывает проблемы, IMO. Меня бы удивило, если бы какие-либо разработчики стандартных библиотек проверяли соответствие в этом конкретном аспекте. - person Angew is no longer proud of SO; 24.02.2015
comment
@Angew @hvd О, я нашел! Включение ‹mutex› позволяет void foo( nullptr_t ) компилироваться нормально. Странный. Очень странно. И добавление любого из ‹cstddef› и ‹stddef.h› перед ‹mutex› переводит тип в пространство имен std. Я почти вижу, к чему все идет. - person Delfigamer; 24.02.2015