Ошибка GCC? Цепочные методы, разбитая точка последовательности

Я некоторое время отлаживал программу и, в конце концов, обнаружил, что ошибка связана с тем, что ссылка не обновляется, как я думал, это будет.

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

#include <iostream>
using namespace std;

struct Test {
    Test& set(int& i){ i = 10; return *this; }
    Test& print(const int& i){ cout << i << endl; return *this; }
};

int main(void){
    int i = 0;
    Test t;

    t.set(i).print(i + 5);

    return 0;
}

Я ожидал, что метод print() будет выводить 15, но вместо этого выводит 5.

EDIT: через 10 дней я понял, что с clang он выводит 15! Это ошибка в GCC?

Ответы

Ответ 1

Позвольте мне попробовать интерпретировать стандарт С++ 11. В §1.9/15 говорится:

За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не имеют никакого значения. [...] Если побочный эффект скалярного объекта не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения, используя значение одного и того же скалярного объекта, поведение undefined.

Конечно, int является скалярным типом, а t.set(i).print(i + 5); содержит побочный эффект на i в set() и вычисление значения i + 5, поэтому, если не указано иное, поведение действительно undefined. Чтение §5.2.5 ( "Доступ к члену класса" ), я не мог найти никаких заметок о последовательностях относительно оператора .. [Но см. Править ниже!]

Обратите внимание, что, конечно, гарантируется, что set() выполняется до print(), потому что последний получает возвращаемое значение первого как (неявный this) аргумент. Суть здесь заключается в том, что вычисление значения для аргументов print является unsequenced неопределенно упорядоченным относительно вызова set.

EDIT: после прочтения ответа в комментарии (@Xeno's), я перечитаю абзац в стандарте, и на самом деле он позже говорит:

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

Поскольку неопределенное упорядочение не является несущественным ( "выполнение нецелевых оценок может перекрываться", §1.9/13), это действительно не поведение undefined, а "просто" неуказанное поведение, а это означает, что и 15, и 5 являются правильными выводами.

Поэтому, когда < означает "секвенирован раньше" и ~ означает "неопределенно упорядоченный", мы имеем:

(value computations for print() arguments ~ execution of set()) < execution of print()

Ответ 2

В С++ нет гарантии относительно порядка, в котором вычисляются аргументы функции в одном выражении, даже если эти функции являются цепными вызовами метода. Вы вызываете здесь поведение undefined и то, что вы получаете.

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

Кроме того, не передавайте int на const int&, так как это может быть быстрее, чем передача int напрямую (если по какой-то странной причине int не вписывается в слово процессора и ссылка.).

Ответ 3

[слишком длинный для комментария:]

Если добавить

Test& add(int& i, const int toadd)
{
  i += toadd;
  return *this;
}

Этот вызов

t.set(i).add(i, 5).print(i);

возвращает

15

Из этого я заключаю, что виновником является параметр i + 5 для печати. ​​