Почему использование std::min в этих выражениях сгиба ведет себя неопределенно?

Благодаря Могу ли я реализовать max(A, max(B , max(C, D))) с использованием выражений свертки? Мне известен один рабочий подход к использованию std::min в выражении свертки (min2 ниже). Однако мне любопытно, почему приведенные ниже подходы min1 и min3 считаются неопределенным поведением (по-видимому, с учетом предупреждения)?

Насколько я понимаю, выражение должно оцениваться в обоих случаях слева направо, постоянно обновляя myMin и присваивая последнее значение обратно myMin. Кроме того, окончательный ответ также всегда верен как для gcc, так и для clang.

template <typename... Args>
auto min1(const Args&... anArgs) {
    constexpr size_t N = sizeof...(anArgs);
    auto myMin = std::get<0>(std::tuple(anArgs...));
    myMin = std::get<N-1>(std::tuple((myMin = std::min(myMin, anArgs))...));
    return myMin;
}

template <typename... Args>
auto min2(const Args&... anArgs) {
    return std::min({anArgs...});
}

template <typename... Args>
auto min3(const Args&... anArgs) {
    auto myMin = (anArgs, ...);
    myMin = ((myMin = std::min(myMin, anArgs)), ...);
    return myMin;
}

Предупреждения:

main.cpp: In instantiation of 'auto min1(const Args& ...) [with Args = {int, int, int}]':
main.cpp:26:30:   required from here
main.cpp:8:45: warning: operation on 'myMin' may be undefined [-Wsequence-point]
    8 |     myMin = std::get<N-1>(std::tuple((myMin = std::min(myMin, anArgs))...));
      |                                      ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:8:45: warning: operation on 'myMin' may be undefined [-Wsequence-point]
main.cpp: In instantiation of 'auto min3(const Args& ...) [with Args = {int, int, int}]':
main.cpp:29:30:   required from here
main.cpp:20:10: warning: left operand of comma operator has no effect [-Wunused-value]
   20 |     auto myMin = (anArgs, ...);
      |          ^~~~~
main.cpp:20:10: warning: left operand of comma operator has no effect [-Wunused-value]
main.cpp:21:11: warning: operation on 'myMin' may be undefined [-Wsequence-point]
   21 |     myMin = ((myMin = std::min(myMin, anArgs)), ...);

Ссылка на Coliru

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


person tangy    schedule 20.02.2021    source источник


Ответы (1)


На самом деле у вас нет неопределенного поведения. Обратите внимание, что предупреждения говорят:

предупреждение: операция с 'myMin' может быть неопределенной [-Wsequence-point]

(мой акцент)

Это предупреждение как в min1, так и в min3 исходит из выражения

((myMin = std::min(myMin, anArgs))...)

Если пакет параметров имеет 3 элемента, как в вашем случае, это выражение будет создано как:

((myMin = std::min(myMin, __anArgs0)) , 
 ((myMin = std::min(myMin, __anArgs1)) , 
  (myMin = std::min(myMin, __anArgs2))))

где __anArgs0 и т. д. — это просто идентификаторы, сгенерированные реализацией. Вы можете увидеть это на cppinsights.

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

Выражения между , не упорядочены, это правда, и поэтому вы получаете предупреждение о том, что все выражение может быть неопределенным. Однако, поскольку не имеет значения, в каком порядке вы вычисляете 3 разных вызова std::min, у вас нет неопределенного поведения, и вы всегда будете получать один и тот же результат.

Однако компилятор не знает этой информации, поэтому выдает предупреждение.

Вы не получите предупреждение с min2, потому что аргументы находятся в списке инициализатора, где порядок оценки аргументов четко определен.

person cigien    schedule 20.02.2021
comment
Выражения между , не имеют последовательности - не совсем так: Правило 9 начинается с C++11 гарантирует порядок вычисления оператора запятой. Наиболее важной гарантией является то, что все побочные эффекты левостороннего выражения находятся в последовательности перед вычислением значения правостороннего оператора, поэтому вы не можете вычислить myMin в двух или всех трех вызовах std::min до применения побочного эффекта std::min. оператор присваивания. - person Sam Varshavchik; 20.02.2021
comment
@SamVarshavchik Хм, похоже, это так. Откуда приходит предупреждение, интересно? Clang также показывает те же предупреждения. - person cigien; 20.02.2021
comment
Бьет меня. Я не вижу никакого неопределенного поведения, основанного на моем прочтении. Думаю, это ложная тревога. - person Sam Varshavchik; 20.02.2021
comment
@SamVarshavchik Да, я тоже не вижу UB. Тот факт, что и Clang, и GCC выдают одно и то же предупреждение, заставляет меня думать, что происходит что-то более глубокое, даже если это ложная тревога. Я должен был более тщательно подумать, прежде чем писать ответ. О, хорошо, я оставлю это, пока я думаю об этом в любом случае. - person cigien; 20.02.2021
comment
Ну, я мог видеть аргумент для предупреждения в первом расширении пакета параметров, как старое предупреждение, которое не применяется. Расширение первого пакета параметров не является выражением со сложенной запятой, оно предоставляет параметры конструктору std::tuple, и когда это интерпретируется как вызов функции до C++11, побочные эффекты параметров вызова функции не являются последовательностями друг с другом. За исключением того, что std::tuples и пакеты параметров не существовали до C++11. - person Sam Varshavchik; 20.02.2021
comment
@SamVarshavchik Я бы понял перенос предупреждения до С++ 11, но это происходит в двух разных компиляторах, что кажется гораздо менее вероятным. Возможно, конечно. - person cigien; 20.02.2021
comment
Я думаю, что это дополнительное задание за пределами фолда. x = x = 1; не был определен до 11. - person T.C.; 20.02.2021
comment
@Т.С. О, я не знал этого о pre-11, спасибо. Как ни странно, gcc по-прежнему предупреждает об этом. Но если это объяснение, то, как и в случае с x=x=1;, предупреждение должно указывать на назначение за пределами сгиба, верно? В данном случае он указывает на внутреннюю. - person cigien; 20.02.2021