ideas icon indicating copy to clipboard operation
ideas copied to clipboard

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

Open kol65536black opened this issue 3 years ago • 14 comments

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

kol65536black avatar Jan 21 '22 09:01 kol65536black

zstring_view

vtopunov avatar Jan 21 '22 10:01 vtopunov

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

pavelkryukov avatar Jan 21 '22 19:01 pavelkryukov

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 строго такой оберткой не является, у него немного другой смысл.

kol65536black avatar Jan 23 '22 06:01 kol65536black

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

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

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

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

pavelkryukov avatar Jan 23 '22 08:01 pavelkryukov

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

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

Smertig avatar Jan 23 '22 08:01 Smertig

Так

vtopunov avatar Jan 23 '22 10:01 vtopunov

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

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

pavelkryukov avatar Jan 23 '22 15:01 pavelkryukov

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

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

char c = 'a'
std::string_view view(&c, 1);

Smertig avatar Jan 23 '22 17:01 Smertig

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

pavelkryukov avatar Jan 23 '22 18:01 pavelkryukov

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

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

vtopunov avatar Jan 23 '22 20:01 vtopunov

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

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

pavelkryukov avatar Jan 23 '22 21:01 pavelkryukov

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

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

kol65536black avatar Jan 24 '22 07:01 kol65536black

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

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

kol65536black avatar Jan 24 '22 07:01 kol65536black

Нашёл прошлое предложение: 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, не выбрасывая размер.

pavelkryukov avatar Jan 29 '22 12:01 pavelkryukov