Ошибка 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.

РЕДАКТИРОВАТЬ: через 10 дней я просто понял, что с помощью clang он выводит 15! Это ошибка в GCC?


person Xeno    schedule 06.02.2014    source источник
comment
Зачем писать такой код, который выглядит некрасиво и всегда будет подвержен ошибкам? Почему требуется объединение методов в цепочку?   -  person Ed Heal    schedule 06.02.2014
comment
@EdHeal У меня есть класс со множеством методов, каждый из которых делает очень мало, и мне нужно вызывать множество из них подряд. Я решил разрешить такую ​​цепочку, чтобы сэкономить немного времени на вводе текста. Теперь, когда я обнаружил такое поведение, я подумаю об удалении цепочки.   -  person Xeno    schedule 06.02.2014
comment
Слишком много спешки, меньше скорости.   -  person Ed Heal    schedule 06.02.2014
comment
Вы передаете 0 + 5 в print(), а затем распечатываете его напрямую. В следующий раз отладьте свой код.   -  person StoryTeller - Unslander Monica    schedule 16.02.2014
comment
@StoryTeller Тогда почему g ++ и clang ++ отличаются по своим выводам?   -  person Xeno    schedule 16.02.2014
comment
@Xeno, потому что временный, который содержит параметр для печати, не должен быть определен после возврата набора. Это неопределенное поведение.   -  person StoryTeller - Unslander Monica    schedule 16.02.2014
comment
@StoryTeller: временного нет. В следующий раз внимательно прочтите код.   -  person Karoly Horvath    schedule 16.02.2014
comment
@KarolyHorvath, что такое i + 5 !?   -  person StoryTeller - Unslander Monica    schedule 16.02.2014
comment
@StoryTeller, вы уверены, что временное значение не нужно определять после возврата набора? оператор точка - это точка последовательности, я думал, что это означает иное.   -  person Xeno    schedule 16.02.2014
comment
@Xano, точка последовательности означает, что все до нее имеет четко определенное значение. Однако в стандарте достаточно места для маневра, чтобы компилятор заранее оптимизировал создание временного объекта для связанного вызова. Если бы у меня был под рукой стандартный, я бы начал цитировать.   -  person StoryTeller - Unslander Monica    schedule 16.02.2014
comment
@Xeno Весь термин «точка последовательности» всегда довольно сбивал с толку. В текущем стандарте он заменен на это с последовательностью до (или после) этого.   -  person Cubic    schedule 16.02.2014


Ответы (3)


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

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

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

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

РЕДАКТИРОВАТЬ: Прочитав ответ в вашем комментарии (@ Xeno), я перечитал абзац в стандарте, и на самом деле позже он говорит:

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

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

Таким образом, когда < означает «упорядоченный до», а ~ означает «неопределенно упорядоченный», мы имеем:

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

person Oberon    schedule 16.02.2014
comment
Этот ответ SO относится к предыдущей версии стандарта C ++ и утверждает, что конец вызова функции является точкой последовательности. Cppreference сообщает, что если между подвыражениями E1 и E2 присутствует точка последовательности, то как вычисление значения, так и побочные эффекты E1 упорядочиваются перед каждым вычислением значения и побочным эффектом E2. Поэтому я не уверен, что существует какое-либо неопределенное поведение (по крайней мере, в соответствии с предыдущим стандартом). - person Xeno; 16.02.2014
comment
@Xeno: Посмотрите еще раз на последний абзац в этом ответе. - person Ben Voigt; 16.02.2014
comment
Это кажется правильным с учетом вашего дополнительного отредактированного объяснения. Возможно, чтобы сделать такое поведение детерминированным, в стандарт следует включить правило «упорядочено до» для связанных методов, подобное оператору запятой, хотя я полагаю, что это потенциально может привести к другим дефектам. - person Xeno; 16.02.2014

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

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

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

person Cubic    schedule 16.02.2014
comment
У меня создалось впечатление, что оператор точка. это точка последовательности, где все до и после должно быть оценено по порядку. - person Xeno; 16.02.2014
comment
@Xeno Ищу. Если это так, извините, и я удалю этот ответ. - person Cubic; 16.02.2014
comment
@Xeno Как я читаю стандарт, единственное, что гарантировано, это то, что вызов set упорядочен до вызова print, я на самом деле не нашел ничего, что подразумевает, что оценка всех аргументов для print должна быть упорядочена после позвонить set. Если бы кто-нибудь мог указать нам на ту часть стандарта, в которой говорится об этом, это действительно было бы ошибкой в ​​gcc. - person Cubic; 16.02.2014
comment
Cppreference указывает, что если точка последовательности присутствует между подвыражениями E1 и E2, тогда как вычисление значения, так и побочные эффекты E1 упорядочиваются перед каждым вычислением значения и побочным эффектом E2. Побочный эффект i = 10 из E1 должен быть упорядочен до вычисления значения i + 5 в E2, не так ли? - person Xeno; 16.02.2014
comment
@Xeno Это должно произойти до вызова print, но не до оценки i+5. - person Cubic; 16.02.2014
comment
Да, ты прав. Думаю, я неверно истолковал, как связаны выражения i + 5 и print. - person Xeno; 16.02.2014

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

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

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

Этот звонок

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

возвращается

15

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

person alk    schedule 16.02.2014