Существует ли гарантированная связь «происходит до» для вычисления аргументов с цепными методами?

#c #trim #undefined-behavior #sequence-points

#c #обрезать #неопределенное поведение #точки последовательности

Вопрос:

Я хочу обрезать строку в C с помощью этого кода:

 std::string str("  Trim test   ");
str.erase(                                 /* 1 */
          0,                               /* 2 */
          str.find_first_not_of(" ")       /* 3 */
         )                                 /* 4 */
   .erase(                                 /* 5 */
          str.find_last_not_of(" ")   1,   /* 6 */
          std::string::npos                /* 7 */
         );                                /* 8 */
 

Позволяет ли стандарт вычислять строку #6 до выполнения строки #1, чтобы при окончательном вызове #5 аргумент мог быть недействительным?

Комментарии:

1. Хорошо. Ни один из этих вопросов, связанных с вызовом / последовательностью функций… ну вот, мы снова здесь!

2. Что ж, если это так очевидно, почему бы не написать ответ?

3. Я никогда не говорил, что это очевидно. Просто то, что это продолжает всплывать — часто приводя к длительным дискуссиям / дебатам / цитированию ссылок / результатам, связанным с конкретной реализацией, и т.д., и т.п. Этот вопрос имеет особенность быть цепным методом по сравнению с «простым» вызовом функции.


Ответ №1:

Да, стандарт позволяет вычислять строку # 6 до выполнения строки # 1.

Со страницы Википедии о точках последовательности:

[Точки последовательности возникают] Перед вводом функции в вызов функции. Порядок, в котором вычисляются аргументы, не указан, но эта точка последовательности означает, что все их побочные эффекты завершаются до ввода функции. В выражении f(i ) g(j ) h(k ) вызывается f с параметром исходного значения i, но i увеличивается перед вводом тела f. Аналогично, j и k обновляются перед вводом g и h соответственно. Однако не указано, в каком порядке выполняются f(), g(), h(), а также в каком порядке i, j, k увеличиваются. Переменные j и k в теле f могут быть или не быть уже увеличены. Обратите внимание, что вызов функции f(a,b,c) не является использованием оператора запятой, и порядок вычисления для a, b и c не определен.

Вы должны переписать его как

 str.erase(0, str.find_first_not_of(" "));           

str.erase(str.find_last_not_of(" ")   1, std::string::npos);     
 

Комментарии:

1. Итак, при чтении цитаты кажется, что «да» отвечает на вопрос «Разрешает ли стандарт вычислять строку # 6 перед строкой # 1», однако это можно легко спутать с ответом на «Существует ли гарантированное отношение «происходит до» […].]». Вам, вероятно, следует уточнить.

Ответ №2:

Да, 6 может быть вызвано до 1.

Однако код можно изменить так, чтобы он не имел значения:

 std::string str("  Trim test   ");
str.erase(                                 /* 1 */
          str.find_last_not_of(" ")   1,   /* 2 */
          std::string::npos                /* 3 */
         )                                 /* 4 */
   .erase(                                 /* 5 */
          0,                               /* 6 */
          str.find_first_not_of(" ")       /* 7 */
         );                                /* 8 */
 

Нет никакой гарантии, что 7 не будет вызван до 1, но это не имеет значения, поскольку индекс все еще действителен.
Имейте в виду, что так много в одной строке — плохая идея, и должно быть не менее 4 отдельных строк.