std::min 带参数包

std::min with Parameter Pack

我在看杰森·特纳的 C++ Weekly - Ep 64 - C++11's std::min (and my version)

现在我开始在这里进行尝试,使用 std::common_type 进行参数包扩展以适用于多种类型:

template <typename T, typename U>
const typename std::common_type<T, U>::type& 
multi_type_min(const T& t, const U& u)
{
   return t < u ? t : u;
}

template<typename First, typename ...T>
typename std::common_type<  First, T...>::type 
variadic_min( const First& f, const T& ...t )
{
    const typename std::common_type<  First, T...>::type* retVal =&f;
    (( retVal= &multi_type_min(*retVal, t)), ...);

    return *retVal;
}

我该如何实现?我是不是在做傻事here

如果你编译时出现警告,你会看到你的错误:

<source>:7:11: warning: returning reference to local temporary object [-Wreturn-stack-address]
   return t < u ? t : u;
          ^~~~~~~~~~~~~
<source>:15:17: note: in instantiation of function template specialization 'multi_type_min<float, unsigned int>' requested here
    (( retVal= &multi_type_min(*retVal, t)), ...);
                ^
<source>:28:14: note: in instantiation of function template specialization 'variadic_min<float, unsigned int, unsigned int, unsigned int>' requested here
      return variadic_min( z, a,b,c );
             ^
1 warning generated.

所以你有未定义的行为,原样。由于 TU 可能不是同一类型,因此它们不能有共同的 reference/pointer。您需要:

  1. 使 multi_type_min return 成为一个值,而不是一个引用。
  2. 使 retVal 成为一个值,而不是指针。

Demo:

#include <type_traits>

template <typename T, typename U>
const typename std::common_type<T, U>::type
multi_type_min(const T& t, const U& u)
{
   return t < u ? t : u;
}

template<typename First, typename ...T>
typename std::common_type<  First, T...>::type 
variadic_min( const First& f, const T& ...t )
{
    typename std::common_type<  First, T...>::type retVal = f;
    (( retVal= multi_type_min(retVal, t)), ...);

    return retVal;
}

int main()
{
    unsigned int a=8, b= 2, c=4;
    float z = 43.42f;

    return variadic_min( z, a,b,c );
}

这是在基于通用类型创建初始化列表后委托给 std::min 的替代实现:

template<typename First, typename ...T>
constexpr
typename std::common_type<First, T...>::type 
common_min(const First& f, const T& ...t) {
    std::initializer_list<typename std::common_type<First, T...>::type> ilist = {
        static_cast<typename std::common_type<First, T...>::type>(f),
        static_cast<typename std::common_type<First, T...>::type>(t)...
    };
    return std::min(ilist);
}

std::common_type 实现的可变参数 std::min 不是一个好主意。

将无符号类型与有符号类型进行比较时会发生什么?您希望已签名的变成未签名的,还是应该将未签名的变成已签名的?如果我们将有符号整数转换为无符号整数,我们就有可能回绕到一个非常大的正值。如果我们将无符号整数转换为有符号整数,我们就有溢出的风险。

当您递归地执行与 std::common_type 的比较时,序列的 std::common_type 可能与两个相邻对之间的 std::common_type 不同。

例如,

auto res = variadic_min(1, -211, 3, -63, 89u);

1, -211, 3, -63, 89ustd::common_typeunsigned int

但是

1, -211std::common_typeint,所以比较时,-211return是最小的。当你比较 -211, 3-211, -63 时也是如此。然而,当你比较-211 89u时,std::common_typeunsigned int,所以-211变成了4294967085,因此89u是最小值,这当然很可笑因为它既不是最小的也不是最大的;只是整数提升的受害者。

如果你想在所有类型中统一使用 common_type,那么你可以这样做:

template<class T, class U>
std::common_type_t<T, U>
variadic_min(const T& t, const U& u)
{
   if (t < u)
        std::cout << t << " less than " << u << std::endl;
    else
        std::cout << u << " less than " << t << std::endl;
    return t < u ? t : u;
}

template<class First, class Second, class... Rest>
std::common_type_t<First, Second, Rest...>
variadic_min(const First& f, const Second& s, const Rest& ...t )
{
    using ret_t = std::common_type_t<First, Second, Rest...>;
    return variadic_min(variadic_min(static_cast<ret_t>(f), static_cast<ret_t>(s)), static_cast<ret_t>(t)...);
}

至少你不会成为整体提升的受害者;您所有的类型都转换为 unsigned int 因此输出至少是可预测的;你所有的负数都会变成大的正数:

输出:

1 less than 4294967085
1 less than 3
1 less than 4294967233
1 less than 89
Res: 1 

最终更好的方法可能是留意 unsigned/signed 比较,然后在这种情况下做一些特别的事情。例如。首先检查带符号的值是否为零,如果小于零,则 return 而不是将其转换为较大的正值。