【问题标题】:Initializer list weirdly depends on order of parameters?初始化列表奇怪地取决于参数的顺序?
【发布时间】:2016-06-19 16:04:09
【问题描述】:

我有以下sn-p的代码:

#include <type_traits>
#include <limits>
#include <initializer_list>
#include <cassert>

template <typename F, typename... FIn>
auto min_on(F f, const FIn&... v) -> typename std::common_type<FIn...>::type
{
  using rettype = typename std::common_type<FIn...>::type;
  rettype result = std::numeric_limits<rettype>::max();
  (void)std::initializer_list<int>{((f(v) < result) ? (result = static_cast<rettype>(v), 0) : 0)...};
  return result;
}

int main()
{
  auto mod2 = [](int a)
  {
    return a % 2;
  };

  assert(min_on(mod2, 2) == 2);     // PASSES as it should
  assert(min_on(mod2, 3) == 3);     // PASSES as it should
  assert(min_on(mod2, 2, 3) == 3);  // PASSES but shouldn't - should be 2
  assert(min_on(mod2, 2, 3) == 2);  // FAILS but shouldn't - should be 2
}

模板函数 min_on 背后的想法是,它应该从传递给它的参数列表中返回参数 x v,以便它为表达式 f(v) 提供最小值。

我观察到的问题是,不知何故,std::initializer_list 中的参数顺序很重要,因此上面的代码将失败,而这段代码:

  assert(min_on(mod2, 3, 2) == 2);

会起作用。这里可能有什么问题?

【问题讨论】:

    标签: c++ c++11 lambda initializer-list typetraits


    【解决方案1】:

    如果f(v) &lt; result,您的函数会将result 设置为v。使用 mod2 作为 ff(v) 只会产生 0、1 或 -1。这意味着如果您的所有值都大于 1,则 result 将设置为最后一个经过测试的 v,因为 f(v) 将始终小于 result。试着把一个负数放在一堆正数中间,不管你放在哪里,负数永远是结果。

    assert(min_on(mod2, 2, 3, 4, -3, 7, 6, 5) == -3);
    

    也许你想要这个:

    std::initializer_list<int>{((f(v) < f(result)) ? (result = static_cast<rettype>(v), 0) : 0)...};
    

    不同之处在于我正在测试f(v) &lt; f(result),而不是f(v) &lt; result。虽然,该函数通常仍然不正确,因为它假定f(std::numeric_limits&lt;rettype&gt;::max()) 是最大可能值。在mod2 的情况下,它可以工作。但是有这样的事情:

    [](int a) { return -a; }
    

    这显然是错误的。所以也许你可以改为要求第一个参数:

    template <typename F, typename FirstT, typename... FIn>
    auto min_on(F f, const FirstT& first, const FIn&... v)
        -> typename std::common_type<FirstT, FIn...>::type
    {
      using rettype = typename std::common_type<FirstT, FIn...>::type;
      rettype result = first;
      (void)std::initializer_list<int>{((f(v) < f(result)) ? (result = static_cast<rettype>(v), 0) : 0)...};
      return result;
    }
    

    或者,如果您想避免对f 的不必要调用:

    template <typename F, typename FirstT, typename... FIn>
    auto min_on(F f, const FirstT& first, const FIn&... v)
        -> typename std::common_type<FirstT, FIn...>::type
    {
      using rettype = typename std::common_type<FirstT, FIn...>::type;
      rettype result = first;
      auto result_trans = f(result);
      auto v_trans = result_trans;
      (void)std::initializer_list<int>{(
        (v_trans = f(v), v_trans < result_trans)
            ? (result = static_cast<rettype>(v), result_trans = v_trans, 0) : 0)...};
      return result;
    }
    

    【讨论】:

    • 虽然存储最佳输入和最佳输出会更有效,以避免重复调用 f。
    • 完美答案!我不能要求更多。非常感谢本杰明
    • 这应该返回一个可选的,或者在空列表上失败。您的建议在编译时因空而失败,这很好。请注意,您可以通过跟踪答案的索引并在找到答案后切换回该值来避免重复复制结果类型(可能很昂贵!),但这是非常先进的。
    猜你喜欢
    • 1970-01-01
    • 2019-04-10
    • 2019-01-05
    • 1970-01-01
    • 2013-08-22
    • 2012-03-19
    • 1970-01-01
    • 2012-02-16
    相关资源
    最近更新 更多