С++ cout побочный эффект

Предположим, что следующий фрагмент кода:

#include <iostream>
using namespace std;

char one()
{
    cout << "one\n";
    return '1';
}

char two()
{
    cout << "two\n";
    return '2';
}

int main(int,char**)
{
    // 1:
    cout << one()
         << '\n'
         << two()
         << '\n';

    // 2:
    operator<<(
        operator<<(
            operator<<(
                operator<<(
                    cout, 
                    one()),
                '\n'),
            two()),
        '\n');
}

выполнение строк, помеченных как 1 и 2, скомпилировано с ideone, делает следующее:

two
one
1
2

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

Это был вопрос на собеседовании, чтобы напечатать выше заданную последовательность (без каких-либо альтернатив), был правильным ответом, но действительно ли это правильно?

Ответ 1

Вы правы, и интервьюер показывает пугающе распространенное отсутствие понимания языка и его правил.

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

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

1.9 Выполнение программы [intro.execution]

[...]
15 [...]
При вызове функции (независимо от того, является ли функция встроенной) каждое вычисление значения и побочный эффект, связанные с любым выражением аргумента, или с выражением postfix, обозначающим вызываемую функцию, секвенированы перед выполнением каждого выражения или выражения в теле называемой функцией. [Примечание. Вычисления значений и побочные эффекты, связанные с разными выражениями аргументов, не имеют никакого значения. -end note] Каждая оценка в вызывающей функции (включая другие вызовы функций), которая иначе не секретируется отдельно до или после выполнения тела вызываемой функции, неопределенно упорядочена в отношении выполнения вызываемая функция .9 Несколько контекстов в С++ вызывают оценку вызова функции, даже если в блоке перевода не появляется синтаксис соответствующих функций. [Пример: оценка нового выражения вызывает одну или несколько функций распределения и конструктора; см. 5.3.4. Для другого примера, вызов функции преобразования (12.3.2) может возникать в контекстах, в которых не появляется синтаксис вызова функции. -end example] Ограничения последовательности для выполнения вызываемой функции (как описано выше) являются функциями вызовов функций, которые оцениваются, независимо от того, какой синтаксис выражения, вызывающего функцию, может быть.

Именование всех частей:

cout << one() // a) execute one()           ("one\n")
              // b) output the return-value ("1")
     << '\n'  // c) output newline          ("\n")
     << two() // d) execute two()           ("two\n")
              // e) output the return-value ("2")
     << '\n'; // f) output newline          ("\n")

Ограничения для заказа:

a < b < c < e < f
d < e < f

Или другое представление:

a < b < c <
          < e < f
d         <

Таким образом, все действительные полные порядки:

abcdef "one\n1\ntwo\n2\n"
abdcef "one\n1two\n\n2\n"
adbcef "one\ntwo\n1\n2\n"
dabcef "two\none\n1\n2\n"

Ответ 2

Вы правы, но ответ на интервью неверен.

В параграфе §1.9/15 стандарта С++ 11:

За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не подвержены.

В качестве примера, это то, что производит Clang 3.4:

one
1
two
2