Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the use of std::min in these fold expressions undefined behavior?

Thanks to Can I implement max(A, max(B, max(C, D))) using fold expressions? I'm aware of one working approach to use std::min in a fold expression(min2 below). However, I'm curious why the below approaches min1 and min3 are considered undefined behavior(seemingly given the warning)?

Per my understanding the expression should evaluate in both cases from left to right, constantly updating myMin and assign the last value back to myMin. Additionally, the final answer is also always correct on both gcc and 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;
}

The warnings are:

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 Link

Finally, the reason I'm looking into alternate approaches(specifically min1) is because I'm trying to use a 3rd party library for which the comma operator is deprecated and I was wondering if this can still be solved using fold expressions.

like image 905
tangy Avatar asked Nov 06 '22 02:11

tangy


1 Answers

You don't actually have any undefined behavior. Note that the warnings say:

warning: operation on 'myMin' may be undefined [-Wsequence-point]

(my emphasis)

This warning, in both min1 and min3, comes from the expression

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

If the parameter pack has 3 elements as it does in your case, this expression will get instantiated as:

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

where __anArgs0 etc are just identifiers generated by the implementation. You can see this on cppinsights.

There's no unsequenced operations in this expression that I can see. I'm not really sure why these warnings are being generated, though I'm fairly sure they're false positives.

The expressions between the , are unsequenced, that is true, and hence you get the warning that the entire expression may be undefined. However, since it doesn't matter in which order you compute the 3 different std::min calls, you don't have undefined behavior, and you are always going to get the same result.

The compiler doesn't know this information though, and so it gives you the warning.

You don't get a warning with min2 because the arguments are within a initalizer list, where the order of evaluation of the arguments is well defined.

like image 152
cigien Avatar answered Nov 15 '22 07:11

cigien