Для перегруженной функции с параметрами указатель и целое число почему передача nullptr и передача нулевого целочисленного литерала могут выбрать разные перегрузки?
nullptr имеет специальный тип std::nullptr_t и предназначен для преобразования в указатель, поэтому выбирается указательная перегрузка. Нулевой литерал 0 имеет тип int: для перегрузки с int он является точным совпадением, а преобразование в указатель выполняется только как преобразование нулевого указателя, поэтому предпочтение получает целочисленная перегрузка.
В старом C++ значение 0 одновременно использовалось как целое число и как нулевой указатель. Это создавало неоднозначность в перегруженных функциях и позволяло случайно выбрать не ту перегрузку.
В C++11 появился nullptr с типом std::nullptr_t. Он отделяет намерение передать нулевой указатель от передачи целочисленного нуля и делает интерфейсы с указателями безопаснее для перегрузки.
Рассмотрим API, где одна перегрузка принимает адрес объекта, а другая — числовой идентификатор. Передача 0 визуально может означать нулевой указатель, но по типу это всё равно int.
Если использовать 0 или зависящий от реализации макрос NULL, компилятор может выбрать числовую перегрузку. Это способно привести не к ошибке компиляции, а к вызову корректной с точки зрения типов, но логически неправильной функции.
Вызов с nullptr рассматривает указательную перегрузку как подходящую через специальное преобразование нулевого указателя. В целочисленный тип nullptr не преобразуется, поэтому числовая перегрузка не участвует.
Литерал 0 имеет тип int. Для параметра int это точное совпадение, а для параметра-указателя требуется преобразование нулевого указателя. При выборе лучшей функции точное совпадение предпочтительнее преобразования, поэтому выбирается перегрузка с int.
nullptr не является макросом и не является целочисленным литералом. Его тип std::nullptr_t можно преобразовать в любой указательный тип или указатель на член, но нельзя неявно преобразовать в обычный целочисленный тип.
Это не означает, что nullptr всегда устраняет неоднозначность: если есть несколько указательных перегрузок, например для int* и double*, вызов с nullptr не содержит информации о требуемом типе и может стать неоднозначным. В таком случае нужен явный static_cast к конкретному типу указателя.
В библиотеке ресурсов существовали перегрузки для поиска ресурса по числовому идентификатору и по адресу дескриптора. Старый код передавал 0, ожидая обозначить отсутствие указателя, но после добавления числовой перегрузки начал вызываться путь поиска по идентификатору.
Можно было оставить 0, полагаясь на правила перегрузки, но это сохраняло неоднозначное намерение и делало код хрупким. Можно было удалить одну из перегрузок, однако это ухудшало удобство API и ломало существующие вызовы.
Выбранным решением стала передача nullptr во всех местах, где требуется отсутствие указателя, а для числового нуля — явный целочисленный тип. В результате выбор перегрузки стал отражать смысл аргумента, а аналогичные ошибки стало проще находить при ревью.
nullptr указателем?Нет. nullptr — это литерал с типом std::nullptr_t, а не объект конкретного указательного типа. Он не хранит адрес и не является void*; его особое свойство состоит в возможности преобразования в нулевое значение любого указательного типа.
Поэтому нельзя считать, что тип nullptr совпадает, например, с int*. При необходимости конкретного типа указателя его можно получить явным приведением.
NULL не является полноценной заменой nullptr?NULL обычно определяется реализацией как нулевой целочисленный литерал или как значение, совместимое с указателем; стандарт не требует единственного текстового определения этого макроса. Если он раскрывается в 0, поведение при перегрузке будет таким же, как у целочисленного нуля.
nullptr имеет гарантированно специальный тип и одинаковую семантику во всех реализациях C++11 и новее. Поэтому в современном C++ для нулевого указателя следует использовать именно его.
Вызов функции, перегруженной для int* и double*, с аргументом nullptr обычно будет неоднозначным: nullptr одинаково подходит для обеих указательных перегрузок, и ни одна из них не является лучшей.
Причина в том, что nullptr сообщает только факт отсутствия указателя, но не его целевой тип. Явное преобразование к int* или double* устраняет неоднозначность, но одновременно фиксирует выбор перегрузки в месте вызова.