初始化列表奇怪地取决于参数的顺序?

时间:2016-06-19 16:04:10

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

我有以下代码片段:

#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,以便它为表达式{{1}提供最小值}。

我观察到的问题是,std::initializer_list内的参数顺序很重要,所以上面的代码会失败,而这段代码:

f(v)

会奏效。这里可能有什么问题?

1 个答案:

答案 0 :(得分:2)

如果result,您的函数会将v设置为f(v) < result。如果mod2ff(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) < f(result),而不是f(v) < result。虽然,该函数通常仍然不正确,因为它假定f(std::numeric_limits<rettype>::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;
}