Как `const std::string& s = nullptr` работает как необязательный параметр

Насколько мне известно, ссылка не может быть нулевой, но когда я запускаю такой код:

#include <iostream>
#include <string>

void test(int i, const std::string& s = nullptr) {
  std::cout << i << " " << s << std::endl;
}

int main() {
  test(1, "test");
  test(2);
}

необязательный параметр s может быть нулевым, и код будет построен. Более того, когда запускается test(2), программа генерирует исключения вместо того, чтобы печатать какие-то случайные строки.

Когда я изменил s на какой-то базовый тип, такой как int, он не смог скомпилироваться, поэтому я думаю, что магия остается внутри строкового класса, но как?

И более того, как я могу проверить, является ли s нулевым или нет? если я использую if(s==nullptr) или if(s.empty()), он не скомпилируется.


person Community    schedule 06.05.2019    source источник
comment
Он компилируется без const?   -  person goodvibration    schedule 06.05.2019
comment
@goodvibration нет, не было, неконстантная ссылка lvalue на тип 'std::string' (также известная как 'basic_string‹char›') не может связываться с временным типом 'nullptr_t'   -  person    schedule 06.05.2019
comment
это не нулевая ссылка, она создает строковый объект s с nullptr в качестве параметра для конструктора string(const char*).   -  person rafix07    schedule 06.05.2019
comment
@ rafix07: Следует ли вызывать конструктор при объявлении ссылочной переменной (и если да, то почему это происходит только тогда, когда эта ссылочная переменная равна const)?   -  person goodvibration    schedule 06.05.2019
comment
Неопределенное поведение... теперь ты в ударе, чувак! Армия зомби-фанатов с неопределенным поведением понизит ваш вопрос до бездны переполнения стека, в соответствии со священной традицией регулирования богохульства с неопределенным поведением.   -  person goodvibration    schedule 06.05.2019
comment
@goodvibration Потому что только константная ссылка на l-значение может быть привязана к временной. Неконстантное не может (l-значение).   -  person Daniel Langr    schedule 06.05.2019
comment
@goodvibration Ссылка на константу может быть привязана к временной (созданной в данном случае с помощью указателя const char*), ссылка на неконстантную не может быть привязана к временной.   -  person Martin Bonner supports Monica    schedule 06.05.2019
comment
@goodvibration Constructor не вызывается, потому что объявлена ​​ссылка. Он вызывается потому, что ссылка привязана к временному объекту, который необходимо построить.   -  person Daniel Langr    schedule 06.05.2019
comment
@goodvibration - Есть хорошие вопросы по UB, а есть плохие. Просто так получилось, что это приличный.   -  person StoryTeller - Unslander Monica    schedule 06.05.2019
comment
@StoryTeller: я согласен. Также есть хорошие участники СО, а есть плохие. Так уж получилось, что большинство относится ко второму типу.   -  person goodvibration    schedule 06.05.2019
comment
Это безумие. Это одна из причин, по которой неявное преобразование отбрасывается в новых (чтобы избежать аргументов, что С++ тоже современен), современных языках.   -  person xinaiz    schedule 06.05.2019


Ответы (2)


test инициализировал свой аргумент с помощью конструктора номер 5 из std::basic_string<char>:

basic_string( const CharT* s,
              const Allocator& alloc = Allocator() );

Поскольку для привязки к этой ссылке необходимо материализовать временное (std::string). Это связано с тем, что ссылка должна быть привязана к объекту правильного типа, а std::nullptr_t — нет. И указанный конструктор имеет ограничение not null на передаваемый указатель. Вызов test без явного аргумента приводит к неопределенному поведению.

Чтобы было совершенно ясно, в правильно построенной программе на C++ не существует такой вещи, как нулевая ссылка. Ссылка должна быть связана с допустимым объектом. Попытка инициализировать один с помощью nullptr будет искать только преобразование.

Поскольку std::string — это объект с четко определенным «пустым» состоянием, фиксированная версия может просто передать инициализированную строку по умолчанию:

void test(int i, const std::string& s = {}); // Empty string by default.

Как только нарушение контракта будет устранено, s.empty() снова должен дать значимые результаты.

person StoryTeller - Unslander Monica    schedule 06.05.2019
comment
возможно, стоит упомянуть об этом явно, просто чтобы быть уверенным: ссылки не могут быть NULL/nullptr - person 463035818_is_not_a_number; 06.05.2019
comment
@ user463035818 - Конечно - person StoryTeller - Unslander Monica; 06.05.2019
comment
Возможно, стоит указать, что автор test вероятно хотел void test(int i, const std::string& s = "") или void test(int i, const std::string& s = std::string{}) - person Martin Bonner supports Monica; 06.05.2019
comment
И как проверить, принимает ли basic_string значение nullptr? Я попробовал string.empty(), но все равно получил исключение. - person ; 06.05.2019
comment
@MartinBonner - И в этом тоже уверен - person StoryTeller - Unslander Monica; 06.05.2019
comment
@user463035818 Релевантно: dcl.ref/5 Примечание. в частности, нулевая ссылка не может существовать в четко определенной программе, потому что единственный способ создать такую ​​ссылку — это связать ее с «объектом», полученным косвенным путем через нулевой указатель, что приводит к неопределенному поведению. - person Swordfish; 06.05.2019
comment
@reavenisadesk Конструктор разыменует nullptr и, скорее всего, вылетит. - person Swordfish; 06.05.2019
comment
@reavenisadesk - вы не можете использовать API типа для проверки нарушения контракта. Контракт должен соблюдаться, чтобы API гарантированно работал. Рассмотрите возможность пошагового выполнения кода, а затем обратитесь к документации. - person StoryTeller - Unslander Monica; 06.05.2019
comment
О, исключение выбрасывается из конструктора? Как только он принимает nullptr в качестве параметра? Если так, то уж точно никак не проверить. - person ; 06.05.2019
comment
@reavenisadesk Это зависит от реализации вашей стандартной библиотеки. Если вы хотите проверить, что происходит, выполните код с помощью отладчика. Стандартная библиотека Microsoft (и я уверен, что каждая) попытается найти длину переданной строки с нулевым завершением, используя strlen(), которая разыменует nullptr и бум. - person Swordfish; 06.05.2019

Ссылка действительно не может быть нулевой, однако const std::string& s = nulltr не делает того, что вы думаете. Когда второй параметр не указан, компилятор создаст строковый объект, вызывающий конструктор строки implicit, который принимает указатель на строку с завершающим нулем в качестве первого параметра. Таким образом, вызов test(2); выглядит следующим образом:

test(2, ::std::string(static_cast<char const *>(nullptr), ::std::string::allocator_type()));

Обратите внимание, что передача nullptr в качестве этого первого параметра приводит к неопределенному поведению.

person user7860670    schedule 06.05.2019