初始化列表奇怪地取决于参数的顺序?
Initializer list weirdly depends on order of parameters?
我有以下代码片段:
#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
背后的想法是它应该 return 传递给它的参数列表中的参数 x
v
以便它给出最小值对于表达式 f(v)
.
我观察到的问题是 std::initializer_list
中参数的顺序不知何故很重要,因此上面的代码将失败,而此代码:
assert(min_on(mod2, 3, 2) == 2);
会起作用。这里可能有什么问题?
如果 f(v) < result
,您的函数将 result
设置为 v
。 mod2
作为 f
,f(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;
}
我有以下代码片段:
#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
背后的想法是它应该 return 传递给它的参数列表中的参数 x
v
以便它给出最小值对于表达式 f(v)
.
我观察到的问题是 std::initializer_list
中参数的顺序不知何故很重要,因此上面的代码将失败,而此代码:
assert(min_on(mod2, 3, 2) == 2);
会起作用。这里可能有什么问题?
如果 f(v) < result
,您的函数将 result
设置为 v
。 mod2
作为 f
,f(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;
}