【问题标题】:Why is the use of std::min in these fold expressions undefined behavior?为什么在这些折叠表达式中使用 std::min 是未定义的行为?
【发布时间】:2021-05-22 23:43:01
【问题描述】:

感谢Can I implement max(A, max(B, max(C, D))) using fold expressions?,我知道在折叠表达式中使用 std::min 的一种工作方法(下面的min2)。但是,我很好奇为什么下面的方法min1min3 被认为是未定义的行为(似乎给出了警告)?

根据我的理解,表达式应该在两种情况下从左到右进行评估,不断更新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 Link

最后,我正在研究替代方法(特别是 min1)的原因是因为我正在尝试使用不推荐使用逗号运算符的 3rd 方库,我想知道是否仍然可以使用折叠表达式来解决这个问题.

【问题讨论】:

    标签: c++ c++17 fold-expression


    【解决方案1】:

    您实际上没有任何未定义的行为。请注意,警告说:

    警告:对“myMin”的操作可能未定义 [-Wsequence-point]

    (我的重点)

    min1min3 中的此警告来自表达式

    ((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 的警告,因为参数位于初始化器列表中,其中参数的评估顺序已明确定义。

    【讨论】:

    • " , 之间的表达式是未排序的" -- 不完全是:Rule 9 从 C++11 开始保证了逗号运算符的求值顺序。最重要的保证是左侧表达式的所有副作用都是在右侧运算符的值计算之前的序列,因此您不能在应用之前对std::min 的两次或全部三个调用中评估myMin赋值运算符的副作用。
    • @SamVarshavchik 嗯,似乎确实如此。我想知道警告来自哪里? Clang 也会显示相同的警告。
    • 击败我。根据我的阅读,我没有看到任何未定义的行为。我认为这是一场虚惊。
    • @SamVarshavchik 是的,我也看不到任何 UB。 Clang 和 GCC 都产生相同的警告这一事实让我认为还有更深层次的事情发生,即使这是一个误报。在写答案之前,我应该更仔细地考虑一下。哦,好吧,反正我会在考虑的时候留下它。
    • 好吧,我可以在第一个参数包扩展中看到警告的参数,作为不适用的旧警告。第一个参数包扩展不是折叠逗号表达式,它为std::tuple 的构造函数提供参数,并且当这被解释为C++ 11 之前的函数调用时,函数调用的参数的副作用不是每个序列的序列其他。除了 std::tuples 和参数包直到 C++11 才存在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多