cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

std::string_ref как обертка над const char* #504

Open kol65536black opened 2 years ago

kol65536black commented 2 years ago

Хотелось бы иметь обертку над const char у которой implicit конструктор от const char, определены операторы сравнения и ряд других полезных функций. Мне кажется странным, но такого нет в библиотеке до сих пор. Может быть есть на то какие-то причины. Хотелось бы обсудить эту тему.

vtopunov commented 2 years ago

zstring_view

pavelkryukov commented 2 years ago

В чём предполагаются отличия от std::string_view?

kol65536black commented 2 years ago

std::string_view имеет больший размер, так-как он должен содержать либо два указателя, либо указатель и размер. Из-за этого больше накладные расходы при передаче, в аргумента функции и т.п.

Указатель/ссылка на "const std::string" выглядят неплохо, но по сути это "указатель на указатель", что требует двойного разыменования.

std::string_view не гарантирует, что ссылается на строку, оканчивающуюся нулем. Поэтому, принимая откуда-то std::string_view и если эту строку надо далее передать в качестве const char*, это нельзя сделать без выделения/копирования данных, даже, если в 99% случаев принимаемый std::string_view действительно ссылается строку с нулем.

Т.е. полностью перейти на "std::string/std::string_view" и отказаться от "const char" нельзя. По крайней мере не заплатив за это цену в виде потери производительности. И в коде "const char" для строк всё равно будет использоваться. Но хочется удобства и единообразия с тем что есть в std. Поэтому хочется иметь обертку над "const char*", класс std::string_view строго такой оберткой не является, у него немного другой смысл.

pavelkryukov commented 2 years ago

Поэтому, принимая откуда-то std::string_view и если эту строку надо далее передать в качестве const char*, это нельзя сделать без выделения/копирования данных

Но std::string_ref здесь не поможет. Данные так или иначе придётся в 1% случаев скопировать (скорее всего на вызывающей стороне; там, что вы назвали «откуда-то»), чтобы получить нуль-терминированную строку, а её можно передать и через std::string_view. Для гарантии перед распадом в const char* можно добавить контракт/assert.

Из-за этого больше накладные расходы при передаче, в аргумента функции и т.п.

Можем ли мы количественно оценить величину этих расходов?

Smertig commented 2 years ago

Для гарантии перед распадом в const char* можно добавить контракт/assert.

Невозможно без UB проверить, является ли произвольный std::string_view нуль-терминированным.

vtopunov commented 2 years ago

Так

pavelkryukov commented 2 years ago

Невозможно без UB проверить, является ли произвольный std::string_view нуль-терминированным.

Может это как-то в стандарт C добавить, хотя бы и не с блестящей производительностью? Понятно, что UB идёт от невозможности проверить, выделена ли память под view.data() + view.size(). Библиотеки это умеют делать, если есть исходный адрес блока: GLIBC, BSD/OSX, Windows, но если указатель куда-то подвинули с точки, которую вернул malloc...

Smertig commented 2 years ago

Понятно, что UB идёт от невозможности проверить, выделена ли память под view.data() + view.size().

Предлагаю также рассмотреть ситуацию, когда о куче не идёт и речи:

char c = 'a'
std::string_view view(&c, 1);
pavelkryukov commented 2 years ago

Большой разницы не вижу. Если память выделена, куча это или стек, то можно "просто" определить, что *(view.data() + view.size()), вызванный внутри особой библиотечной функции не есть UB (в кавычках, потому что будут разные интересные последствия для оптимизаций, конечно). Проблемы начинаются, если чтение по этой памяти технически невозможно, т. е. делает SIGSERV или вообще лезет в какое-нибудь устройство...

vtopunov commented 2 years ago

Большой разницы не вижу. Если память выделена, куча это или стек, то можно "просто" определить, что *(view.data() + view.size()), вызванный внутри особой библиотечной функции не есть UB (в кавычках, потому что будут разные интересные последствия для оптимизаций, конечно). Проблемы начинаются, если чтение по этой памяти технически невозможно, т. е. делает SIGSERV или вообще лезет в какое-нибудь устройство...

Тогда несколько конструкторов у std::string_view придется выпилить. Интересно, почему сразу так не сделали?

pavelkryukov commented 2 years ago

Потому что текущий std::string_view реализован чисто языковыми конструкциями; то, что я предложил, требует поддержки минимум от libc и, что более вероятно, ОС, и будет работать небыстро. Но как инструмент для валидации это было бы полезно.

Поэтому давайте вернёмся к обсуждению исходного предложения.

kol65536black commented 2 years ago

Можем ли мы количественно оценить величину этих расходов?

Я количественно оценить не готов. Но на каждый такой std::string_view нужно два (указателя/целых числа) вместо одного (для string_ref), быстрее закончатся регистры процессора и аргументы функции придется передавать через стек.

kol65536black commented 2 years ago

Но std::string_ref здесь не поможет. Данные так или иначе придётся в 1% случаев скопировать

Ну вот пусть оно в 1% случаев копирует с вызывающей стороны, чем копирует в 100% случаев в вызываемом коде.

pavelkryukov commented 2 years ago

Нашёл прошлое предложение: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1402r0.pdf Результаты голосования: https://github.com/cplusplus/papers/issues/189

нужно два (указателя/целых числа) вместо одного (для string_ref), быстрее закончатся регистры процессора

Обычно как компилятор, так и железо хорошо этот процесс оптимизируют. Поэтому без количественных данных выглядит как premature optimization. Кстати, в PR1402 предлагалось просто обернуть std::string_view, не выбрасывая размер.