Вызов функции-инварианта аргумента-перестановки f(i++, i++)
Предположим, у меня есть функция foo(x, y, z)
это инвариант относительно всех перестановок его аргументов. У меня тоже есть итератор it
так, что итераторы it
, it + 1
а также it + 2
может быть разыменовано.
Это нормально писать
... = foo(*it++, *it++, *it++); // (1)
вместо
... = foo(*it, *(it + 1), *(it + 2)); // (2)
?
Насколько я понимаю, технически это правильно, так как C++17 из-за (цитируя cppreference.com и принимая во внимание, что it
может быть сырой указатель)
15) При вызове функции вычисления значений и побочные эффекты инициализации каждого параметра определяются неопределенным образом по отношению к вычислениям значений и побочным эффектам любого другого параметра.
Порядок оценки аргументов функции не определен, но для foo()
порядок не имеет значения.
Но это приемлемый стиль кодирования? С одной стороны, (1)
хорошо симметрично и подразумевает, что foo
имеет такую неизменность, и (2)
выглядит несколько некрасиво С другой стороны, (1)
немедленно ставит вопрос о его правильности - тот, кто читает код, должен проверить описание или определение foo
проверить правильность звонка.
Если тело foo()
мал, и инвариантность очевидна из определения функции, вы бы приняли (1)
?
(Возможно, этот вопрос основан на мнении. Но я не могу не задать его.)
1 ответ
Вы правы в том, что в C++17
foo(*it++, *it++, *it++);
не неопределенное поведение. Как говорится в вашей цитате и как указано в [expr.call]/8
Постфиксное выражение упорядочивается перед каждым выражением в списке выражений и любым аргументом по умолчанию. Инициализация параметра, включая каждое связанное с ним вычисление значения и побочный эффект, определяется неопределенным образом относительно последовательности любого другого параметра.
каждый шаг упорядочен, поэтому у вас нет нескольких непоследовательных записей. Порядок вычисления аргументов функции все еще не определен в C++17, так что это означает, что у вас просто неопределенное поведение (вы не можете знать, какой элемент передается каждому аргументу).
Пока ваша функция не заботится об этом, вы в порядке. Если порядок аргументов будет иметь значение, вам придется использовать вторую версию.
Это все сказал, что я предпочел бы использование
foo(*it, *(it + 1), *(it + 2));
даже если порядок не имеет значения. Это делает код обратно совместимым, и, по-моему, его легче рассуждать. Я бы предпочел увидеть it += 3
в инкрементной части цикла for выполняется многократное увеличение в вызове функции, а в инкрементной части цикла нет приращения.