带参数包的std :: min

时间:2017-05-23 04:33:18

标签: c++ c++17

我在看Jason Turner的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吗?

2 个答案:

答案 0 :(得分:3)

如果您使用警告进行编译,则会发现您的错误:

<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可能不是同一类型,因此您无法同时使用它们的公共引用/指针。你需要:

  1. multi_type_min返回一个值,而不是引用。
  2. 使retVal成为值,而不是指针。
  3. 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);
    }
    

答案 1 :(得分:3)

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

将无符号类型与已签名类型进行比较会发生什么?您是否希望签名的未签名,或者未签名的签名?如果我们将有符号整数转换为无符号整数,我们就有可能卷入一个非常大的正值。如果我们将无符号整数转换为有符号整数,我们就有可能溢出。

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

例如,

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

std::common_type的{​​{1}}为1, -211, 3, -63, 89u

BUT

unsigned int的{​​{1}}为std::common_type,因此当您比较它们时,您将获得1, -211作为最低要求。比较int-211时也是如此。但是,当您比较-211, 3时,-211, -63-211 89u,因此std::common_type变为4294967085,因此unsigned int是最小值,这当然是荒谬的因为它既不是最小的也不是最大的;只是整数推广的受害者。

如果你想在所有类型中统一使用-211,那么你可能会这样做:

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)...);
}

最后一种更好的方法可能是寻找未签名/签名的比较,然后在这种情况下做一些特别的事情。例如。首先检查签名值是否为零,如果它更小,则返回该值,而不是将其转换为大的正值。