Что произойдет, если аргументы будут псевдонимом выходного буфера в std::format_to?

Рассмотрим следующий пример:

#include <format>
#include <print>

int main() {
    int x = 12345;
    // note: this is "awoo" followed by 28 zeros ([dcl.init.string] p3)
    char buffer[32] = "awoo";
    std::format_to(buffer, "{}{}", x, buffer);
    std::println("{}", buffer);
}

Ожидается, что этот код выведет 12345awoo, потому что мы форматируем {}{} с аргументами 12345 и "awoo".

Однако фактический результат равен 1234512345. Причина этого очевидна: форматирование 12345 будет записывать в буфер, и к тому времени, когда buffer (второй аргумент) будет обработан std::format_to, он больше не будет содержать "awoo".

Возникает вопрос: каков ожидаемый результат согласно стандарту C++? https://eel.is/c++draft/format.functions#lib:vformat_to не указывает в качестве предварительного условия, что псевдонимы между аргументами и выходным буфером не разрешены, поэтому я не верю в это в этом случае поведение неопределенное.

Использование одного и того же буфера в качестве источника ввода и назначения вывода считается неопределенным поведением.

OldBoy 24.04.2024 12:55

@OldBoy, у тебя есть стандартная цитата, подтверждающая это? Это, конечно, верно для некоторых функций, таких как std::memcpy, но это необходимо указать в предусловиях функции. Это не так, если не указано иное.

Jan Schultke 24.04.2024 12:56

Ожидаемый результат (из коробки, порядок оценки) — 1234512345. Еще интереснее, есть ли у Клинта и подобные замечания вещи. Интересно, требуется ли оценка to_format для добавления завершающего \0 при заполнении buffer. Когда нет, эффект мог бы быть таким "1234512345hu#+hi-.dfhd"

Joop Eggen 24.04.2024 12:57

@JoopEggen to_format принимает только выходной итератор и не знает остальную часть буфера, поэтому он не может писать дальше конца 1234512345 без риска сбоя сегментации. Как вы думаете, почему ожидаемый результат равен 1234512345? Есть ли какая-то гарантия, что входные аргументы обрабатываются один за другим, слева направо, и одновременно продвигается выходной итератор?

Jan Schultke 24.04.2024 13:00

@JoopEggen, в \0 куча buffer, поэтому to_format добавлять не нужно

Caleth 24.04.2024 13:02
buffer мог содержать "awoo\0u#+hi-.dfhd\0..." (32 места или ошибка сегмента), тогда первый аргумент int дает "12345u#+hi-.dfhd\0..." с выходным итератором с индексом 5, а затем окончательный результат.
Joop Eggen 24.04.2024 13:37

@JoopEggen мы на одной волне? В другом примере да, гипотетически может быть awoo\0xzy..., но в этом примере все после awoo инициализируется нулями (см. eel.is/c++draft/dcl.init.string#3).

Jan Schultke 24.04.2024 13:40

@Caleth buffer обычно не заполняется \0 (если в спецификации не было внесено изменений). Компилятор Microsoft имел привычку заполнять \0s

Joop Eggen 24.04.2024 13:41

Хорошо, я должен извиниться перед некоторыми людьми. @ЯнШультке

Joop Eggen 24.04.2024 13:43
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
9
175
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это не указано, оба 1234512345 и 12345awoo соответствуют результатам.

Функции, определенные в [format.functions], используют специализации средства форматирования шаблонов классов для форматирования отдельных аргументов.

[format.formatter.spec#1]

Таким образом, форматирование каждого аргумента происходит внутри отдельного вызова функции для остальных. Нет никаких требований к порядку этих вызовов, поэтому побочные эффекты неопределенно последовательны друг с другом.

Это довольно натянуто, но даже разумные реализации могут привести к неопределенному поведению 12345awoo. Не исключено, что супероптимизированная реализация, получив std::uint8_t, «отформатирует» его через таблицу поиска, загрузив результат в регистр, чтобы не нужно было немедленно записывать его в буфер. Если бы он распознал, что awoo — короткая строка, он, возможно, также мог бы загрузить ее в регистр, объединить две строки в регистре xmm с помощью побитового сдвига, а затем записать все, используя одну инструкцию mov mem, xmm0.

Jan Schultke 24.04.2024 13:38

Почти уверен, что вызовы должны выполняться по порядку... откуда вы можете знать, где форматировать N+1-й аргумент, если вы не знаете, сколько байтов заняли первые N-аргументы (чего вы, вообще-то, не знаете) )?

Barry 24.04.2024 15:18

@Барри, да, посмотри историю редактирования этого ответа. В большинстве случаев вы этого не делаете, но могут возникнуть ситуации, когда умный компилятор может сэкономить несколько циклов, выполнив все по-другому.

Caleth 24.04.2024 15:21

@Caleth Умный компилятор не может. Умная библиотека... потенциально может быть в некоторых очень узких ситуациях с неясной выгодой?

Barry 24.04.2024 16:14

@Барри, извини, умная реализация может

Caleth 24.04.2024 16:16

@Барри, тебе вроде как нужно знать, сколько символов приняли предыдущие аргументы, но это не значит, что тебе нужно немедленно записывать все в выходной буфер. При форматировании группы std::uint8_t вы также можете буферизовать отформатированные байты в регистрах, объединить их в регистр xmm и выполнить одиночный mov mem, xmm0 в самом конце. Другими словами, стандарт не налагает никаких требований относительно того, когда выходной итератор продвигается вперед и как это происходит с точки зрения форматирования аргументов. Реализация может использовать это, особенно если этот итератор непрерывен.

Jan Schultke 25.04.2024 09:14

Теперь это LWG Issue 4078.

Хотя результат в настоящее время не указан: 1234512345 или 12345awoo (как объяснено в ответе @Caleth), этот случай также не должен работать; то есть это должно быть неопределенное поведение.

Подобные функции, такие как sprintf, имеют параметр char *restrict buffer для вывода; они не поддерживают псевдонимы между аргументами и выходным буфером. Пользователю неразумно ожидать, что это будет работать, и неразумно, чтобы реализация поддерживала этот вариант использования.

В какой-то момент std::format_to(buffer, "{}{}", x, buffer);, скорее всего, станет неопределённым поведением, однако это может занять много времени, поскольку сложно придумать формулировку, запрещающую этот вариант использования, и это не является проблемой высокого приоритета.

Другие вопросы по теме