Является ли этот код четким?

Этот код берется из обсуждения здесь.

someInstance.Fun(++k).Gun(10).Sun(k).Tun();

Является ли этот код четким? Является ли ++k в Fun() оцененным до k в Sun()?

Что делать, если k является определяемым пользователем типом, а не встроенным типом? И каким образом вышеупомянутая функция вызывает порядок, отличается от этого:

eat(++k);drink(10);sleep(k);

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

В разделе 1.9.17 стандарта С++ ISO говорится о точках последовательности и оценке функции:

При вызове функции (или не функция является встроенной), есть точка после оценки всех аргументов функции (если есть) которое происходит до любые выражения или утверждения в функция корпус. Существует также точка последовательности после копирования возвращаемое значение и до выполнение любых выражений вне функция.

Ответ 1

Это зависит от того, как определяется Sun. Следующее хорошо определено

struct A {
  A &Fun(int);
  A &Gun(int);
  A &Sun(int&);
  A &Tun();
};

void g() {
  A someInstance;
  int k = 0;
  someInstance.Fun(++k).Gun(10).Sun(k).Tun();
}

Если вы измените тип параметра Sun на int, он станет undefined. Пусть нарисует дерево версии с int.

                     <eval body of Fun>
                             |
                             % // pre-call sequence point
                             | 
 { S(increment, k) }  <-  E(++x) 
                             |     
                      E(Fun(++k).Gun(10))
                             |
                      .------+-----.       .-- V(k)--%--<eval body of Sun>
                     /              \     /
                   E(Fun(++k).Gun(10).Sun(k))
                              |
                    .---------+---------. 
                   /                     \ 
                 E(Fun(++k).Gun(10).Sun(k).Tun())
                              |
                              % // full-expression sequence point

Как видно, мы читаем k (обозначается V(k)) и побочный эффект на k (в самом верху), которые не разделены точкой последовательности: В этом выражении, по отношению друг к другу суб-выражение, нет точки последовательности вообще. Самое нижнее % означает точку последовательности полного выражения.

Ответ 2

Я думаю, что если вы точно прочтете, что говорит эта стандартная цитата, первый случай не будет четко определен:

При вызове функции (независимо от того, является ли функция встроенной), после оценки всех аргументов функции (если они есть) есть точка последовательности, которая выполняется перед выполнением любых выражений или операторов в теле функции

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

Но если вы представляете себе такой случай:

foo(X).bar(Y)

единственная гарантия, которую это дает нам, такова:

  • X оценивается перед вызовом foo и
  • Y оценивается перед вызовом bar.

Но такой порядок все равно будет возможен:

  • оцените X
  • evalute Y
  • (точка последовательности, отделяющая X от вызова foo)
  • вызов foo
  • (точка последовательности, отделяющая Y от вызова bar)
  • вызов bar

и, конечно же, мы могли бы поменять местами первые два элемента, оценивая Y до X. Почему нет? Стандарт требует только того, чтобы аргументы для функции полностью оценивались до первого утверждения тела функции, и приведенные выше последовательности удовлетворяли этому требованию.

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

Ответ 3

Это поведение undefined, так как значение k изменяется и считывается в одном выражении без промежуточной точки последовательности. См. Отличный длинный ответ на этот вопрос.

Цитата из 1.9.17 сообщает вам, что все аргументы функции вычисляются до того, как вызывается тело функции, но ничего не говорит об относительном порядке оценки аргументов для разных вызовов функций в одном выражении - нет гарантии, что "++ k Fun() оценивается до k в Sun()".

eat(++k);drink(10);sleep(k);

отличается тем, что ; является точкой последовательности, поэтому порядок оценки хорошо определен.

Ответ 4

В качестве небольшого теста рассмотрим:

#include <iostream>

struct X
{
    const X& f(int n) const
    {
        std::cout << n << '\n';
        return *this;
    }
};

int main()
{
    int n = 1;

    X x;

    x.f(++n).f(++n).f(++n).f(++n);
}

Я запускаю это с gcc 3.4.6 и не оптимизирую и получаю:

5
4
3
2

... с -O3...

2
3
4
5

Таким образом, либо версия 3.4.6 имела серьезную ошибку (что немного сложно поверить), либо последовательность undefined, как предположил Филипп Поттер. (GCC 4.1.1 с/без -O3 производится 5, 5, 5, 5.)

EDIT - мое резюме обсуждения в комментариях ниже:

  • 3.4.6 действительно может быть ошибка (ну да)
  • Многие новые компиляторы производят 5/5/5/5... это определенное поведение?
    • возможно, нет, так как он соответствует всем приращим побочным эффектам, которые "действуют" до того, как будут сделаны какие-либо вызовы функций, что не является поведением, которое любой из предложенных здесь может гарантировать стандартным
  • Это не очень хороший подход к исследованию стандартных требований (в частности, с более старым компилятором, например, 3.4.6): согласовано, но это полезная проверка работоспособности

Ответ 5

Я знаю, что поведение компиляторов ничего не может доказать, но я подумал, что было бы интересно узнать, что даст внутреннее представление компилятора (еще немного выше уровня проверки сборки).

Я использовал демо-версию Clang/LLVM онлайн с помощью этого кода:

#include <stdio.h>
#include <stdlib.h>

struct X
{
  X const& f(int i) const
  {
    printf("%d\n", i);
    return *this;
  }
};

int main(int argc, char **argv) {
  int i = 0;
  X x;
  x.f(++i).f(++i).f(++i);         // line 16
}

И скомпилированный с помощью стандартных оптимизаций (в режиме С++), он дал:

/tmp/webcompile/_13371_0.cc: В функции 'int main (int, char **)':
/tmp/webcompile/_13371_0.cc:16: предупреждение: операция на 'i' может быть undefined

который я нашел интересным (предупреждал ли кто-нибудь из других компиляторов об этом? Комо онлайн не делал)


В качестве альтернативы также было создано следующее промежуточное представление (прокрутить вправо):

@.str = private constant [4 x i8] c"%d\0A\00", align 1 ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  %0 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %1 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  %2 = tail call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 3) nounwind ; <i32> [#uses=0]
                                                                                                             ^^^^^
  ret i32 0
}

По-видимому, Clang ведет себя как gcc 4.x.x и сначала оценивает все аргументы перед выполнением любого вызова функции.

Ответ 6

Второй случай определенно определен. Строка токенов, заканчивающаяся точкой с запятой, является атомарным выражением в С++. Каждый оператор анализируется, обрабатывается и завершается до начала следующего утверждения.