实现可变最小/最大函数 [英] Implementing variadic min / max functions

查看:140
本文介绍了实现可变最小/最大函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现可变最小/最大函数。目标是利用编译时已知数量的参数,并执行展开的评估(避免运行时循环)。代码的当前状态如下(呈现min-max是类似的)

  #include< iostream> 

using namespace std;

template< typename T>
T vmin(T val1,T val2)
{
return val1< val2? val1:va​​l2;
}

template< typename T,typename ... Ts>
T vmin(T val1,T val2,Ts& ... vs)
{
return val1< val2?
vmin(val1,std :: forward< Ts>(vs)...):
vmin(val2,std :: forward&
}

int main()
{
cout<< vmin(3,2,1,2,5)<< endl;
cout<< vmin(3,1.2,1.3,2,5.2) endl;
return 0;
}

现在 This works ,但我有一些问题/问题


  1. 不可变重载必须按值接受其参数。如果我尝试传递其他类型的引用,我有以下结果




    • 通用引用&& c $ c> - >编译错误

    • const引用 const& - > OK

    • 纯文字& - >编译错误



    href =http://stackoverflow.com/a/22411782/2567683>功能模板混合奇怪与模板,但有什么具体的技术诀窍在手边? 我应该选择什么类型的参数?


  2. 参数包的扩展不够吗?我真的需要转发我的参数到递归调用吗?


  3. 当包装在结构中并作为静态成员函数公开时,这个函数更好地实现。部分专业的能力给我买什么?


  4. 功能版本有更强大/高效的实施/设计吗? (特别是我想知道 constexpr 版本是否会匹配模板元编程的效率)



解决方案

实例



这对参数完美转发。它依赖于RVO的返回值,因为它返回一个值类型而不考虑输入类型,因为 common_type 就是这样。



我实现了 common_type 扣除,允许传入混合类型,并输出预期结果类型。



我们支持最少1个元素,因为它使代码更加轻盈。

  #include< utility> 
#include< type_traits>

template< typename T>
T vmin(T& t)
{
return std :: forward< T&
}

template< typename T0,typename T1,typename ... Ts>
typename std :: common_type<
T0,T1,Ts ...
> :: type vmin(T0&& val1,T1&& val2,Ts&& ... vs)
{
if(val2 return vmin(val2,std :: forward< Ts>(vs)...)
else
return vmin(val1,std :: forward< Ts>(vs)...)
}


int main()
{
std :: cout< vmin(3,2,0.9,2,5)<< std :: endl;

std :: cout<< vmin(3,1.2,1.3,2,5.2) std :: endl;

return 0;
}

现在,虽然上面是一个完全可以接受的解决方案,



表达式((a< b)?a:b)= 7 是合法的C ++, code> std :: common_type decay vmin(a,b)= 7 code> s是参数盲目的(由于我认为对它返回右值引用的过度反应,当在 std :: common_type

简单地使用 decltype(true?a:b)是诱人的,右值引用问题,并且不支持 common_type 专业化(作为示例, std :: chrono )。所以我们都想使用 common_type ,不想使用它。



其次,写一个 min 函数不支持不相关的指针,并且不允许用户更改比较函数似乎错误。



是上面更复杂的版本。 实例

  #include< ; iostream> 
#include< utility>
#include< type_traits>

命名空间my_min {

//一个common_type,当进给lvalue引用所有相同类型时,返回所有相同类型的lvalue引用
//但是,它是聪明到足以能够理解common_type专业化。这在标准中绕着一个quirk
//工作,其中(true?x:y)是一个左值引用,而common_type< X,Y> :: type不是。
template< typename ... Ts>
struct my_common_type;

template< typename T>
struct my_common_type< T> {typedef T type;};

template< typename T0,typename T1,typename ... Ts>
struct my_common_type< T0,T1,Ts ...> {
typedef typename std :: common_type< T0,T1> :: type std_type;
//如果类型相同,不要改变它们,与common_type不同:
typedef typename std :: conditional< std :: is_same< T0,T1> :: value,
T0,
std_type> :: type working_type;
//小心!我们不想返回右值引用。只需返回T:
typedef typename std :: conditional<
std :: is_rvalue_reference< working_type> :: value,
typename std :: decay< working_type> :: type,
working_type
> :: type common_type_for_first_two;
// TODO:what's base&和Derived& ;?返回基础&可能是正确的事情。
//另一方面,鼓励无声切片。所以也许不是。
typedef typename my_common_type& common_type_for_first_two,Ts ...> :: type type;
};
template< typename ... Ts>
using my_common_type_t = typename my_common_type< Ts ...> :: type;
//如果t是一个右值,则返回一个值类型:
template< typename Picker,typename T>
T pick(Picker& / * unused * /,T& t)
{
return std :: forward< T&
}
//轻微的优化将使得Picker在实际的2-arg情况下被前向调用,但我不在乎:
template< typename Picker,typename T0,typename T1,类型名... Ts>
my_common_type_t< T0,T1,Ts ...> $ pick $ pick(picker&&picker,T0&&& val1,T1&&&&&&&&&> vs)
{
//如果picker不喜欢2超过1,使用1 - 稳定!
if(picker(val2,val1))
return pick(std :: forward< Picker>(pick),val2,std :: forward&
else
return pick(std :: forward< Picker>(pick),val1,std :: forward< Ts>(vs)...)
}

//可以用less替换< void>在C ++ 1y?
struct lesser {
template< typename LHS,typename RHS>
bool operator()(LHS& amp; lhs,RHS&&&rhs)const {
return std :: less< typename std :: decay< my_common_type_t< LHS,RHS> :: type>()(
std :: forward< LHS>(lhs),std :: forward< RHS>(rhs)
);
}
};
//简单地转发给具有智能小于函子的picked_min函数
//注意我们支持不相关的指针!
template< typename ... Ts>
auto min(Ts& ... ts) - > decltype(pick(less(),std :: declval< Ts>()...))
{
return(lower(),std :: forward< Ts>(ts)...);
}
}

int main()
{
int x = 7;
int y = 3;
int z = -1;
my_min :: min(x,y,z)= 2;
std :: cout<< x < ,< y < ,< z < \\\
;
std :: cout<< my_min :: min(3,2,0.9,2,5)<< std :: endl;
std :: cout<< my_min :: min(3,1.2,1.3,2,5.2)< std :: endl;
return 0;
}

上述实现的缺点是大多数类不支持 operator =(T const&)&& = delete - 即,它们不会阻止赋值赋值, code> min 不会。基本类型是



这是一个附注:开始删除您的右值引用 operator = 的人。 p>

I'm implementing variadic min/max functions. A goal is to take advantage of the compile time known number of arguments and perform an unrolled evaluation (avoid run-time loops). The current state of the code is as follows (presenting min - max is similar)

#include <iostream>  

using namespace std;

template<typename T>
T vmin(T val1, T val2)
{
    return val1 < val2 ? val1 : val2;
}

template<typename T, typename... Ts>
T vmin(T val1, T val2, Ts&&... vs)
{
    return val1 < val2 ?
        vmin(val1, std::forward<Ts>(vs)...) : 
            vmin(val2, std::forward<Ts>(vs)...);
}

int main()
{
    cout << vmin(3, 2, 1, 2, 5) << endl;    
    cout << vmin(3., 1.2, 1.3, 2., 5.2) << endl;
    return 0;
}

Now this works, but I have some questions / problems :

  1. The non variadic overload has to accept its arguments by value. If I try passing other types of ref I have the following results

    • universal references && -> compilation error
    • const references const& -> OK
    • plain references & -> compilation error

    Now I know that function templates mix weirdly with templates but is there any specific know-how for the mix up at hand ? What type of arguments should I opt for?

  2. Wouldn't the expansion of the parameter pack by sufficient ? Do I really need to forward my arguments to the recursive call ?

  3. Is this functionallity better implemented when wrapped inside a struct and exposed as a static member function. Would the ability to partial specialize buy me anything ?

  4. Is there a more robust/efficient implementation/design for the function version ? (particullarly I'm wondering whether a constexpr version would be a match for the efficiency of template metaprogramming)

解决方案

live example

This does perfect forwarding on arguments. It relies on RVO for return values, as it returns a value type regardless of the input types, because common_type does that.

I implemented common_type deduction, allowing mixed types to be passed in, and the "expected" result type output.

We support the min of 1 element, because it makes the code slicker.

#include <utility>
#include <type_traits>

template<typename T>
T vmin(T&&t)
{
  return std::forward<T>(t);
}

template<typename T0, typename T1, typename... Ts>
typename std::common_type<
  T0, T1, Ts...
>::type vmin(T0&& val1, T1&& val2, Ts&&... vs)
{
  if (val2 < val1)
    return vmin(val2, std::forward<Ts>(vs)...);
  else
    return vmin(val1, std::forward<Ts>(vs)...);
}


int main()
{
  std::cout << vmin(3, 2, 0.9, 2, 5) << std::endl;

  std::cout << vmin(3., 1.2, 1.3, 2., 5.2) << std::endl;

  return 0;
}

Now, while the above is a perfectly acceptable solution, it isn't ideal.

The expression ((a<b)?a:b) = 7 is legal C++, but vmin( a, b ) = 7 is not, because std::common_type decays is arguments blindly (caused by what I consider an over-reaction to it returning rvalue references when fed two value-types in an older implementation of std::common_type).

Simply using decltype( true?a:b ) is tempting, but it both results in the rvalue reference problem, and does not support common_type specializations (as an example, std::chrono). So we both want to use common_type and do not want to use it.

Secondly, writing a min function that doesn't support unrelated pointers and does not let the user change the comparison function seems wrong.

So what follows is a more complex version of the above. live example:

#include <iostream>
#include <utility>
#include <type_traits>

namespace my_min {

  // a common_type that when fed lvalue references all of the same type, returns an lvalue reference all of the same type
  // however, it is smart enough to also understand common_type specializations.  This works around a quirk
  // in the standard, where (true?x:y) is an lvalue reference, while common_type< X, Y >::type is not.
  template<typename... Ts>
  struct my_common_type;

  template<typename T>
  struct my_common_type<T>{typedef T type;};

  template<typename T0, typename T1, typename... Ts>
  struct my_common_type<T0, T1, Ts...> {
    typedef typename std::common_type<T0, T1>::type std_type;
    // if the types are the same, don't change them, unlike what common_type does:
    typedef typename std::conditional< std::is_same< T0, T1 >::value,
      T0,
    std_type >::type working_type;
    // Careful!  We do NOT want to return an rvalue reference.  Just return T:
    typedef typename std::conditional<
      std::is_rvalue_reference< working_type >::value,
      typename std::decay< working_type >::type,
      working_type
    >::type common_type_for_first_two;
    // TODO: what about Base& and Derived&?  Returning a Base& might be the right thing to do.
    // on the other hand, that encourages silent slicing.  So maybe not.
    typedef typename my_common_type< common_type_for_first_two, Ts... >::type type;
  };
  template<typename... Ts>
  using my_common_type_t = typename my_common_type<Ts...>::type;
  // not that this returns a value type if t is an rvalue:
  template<typename Picker, typename T>
  T pick(Picker&& /*unused*/, T&&t)
  {
    return std::forward<T>(t);
  }
  // slight optimization would be to make Picker be forward-called at the actual 2-arg case, but I don't care:
  template<typename Picker, typename T0, typename T1, typename... Ts>
  my_common_type_t< T0, T1, Ts...> pick(Picker&& picker, T0&& val1, T1&& val2, Ts&&... vs)
  {
    // if picker doesn't prefer 2 over 1, use 1 -- stability!
    if (picker(val2, val1))
      return pick(std::forward<Picker>(pick), val2, std::forward<Ts>(vs)...);
    else
      return pick(std::forward<Picker>(pick), val1, std::forward<Ts>(vs)...);
  }

  // possibly replace with less<void> in C++1y?
  struct lesser {
    template<typename LHS, typename RHS>
    bool operator()( LHS&& lhs, RHS&& rhs ) const {
      return std::less< typename std::decay<my_common_type_t<LHS, RHS>>::type >()(
          std::forward<LHS>(lhs), std::forward<RHS>(rhs)
      );
    }
  };
  // simply forward to the picked_min function with a smart less than functor
  // note that we support unrelated pointers!
  template<typename... Ts>
  auto min( Ts&&... ts )->decltype( pick( lesser(), std::declval<Ts>()... ) )
  {
    return pick( lesser(), std::forward<Ts>(ts)... );
  }
}

int main()
{
  int x = 7;
  int y = 3;
  int z = -1;
  my_min::min(x, y, z) = 2;
  std::cout << x << "," << y << "," << z << "\n";
  std::cout << my_min::min(3, 2, 0.9, 2, 5) << std::endl;
  std::cout << my_min::min(3., 1.2, 1.3, 2., 5.2) << std::endl;
  return 0;
}

The downside to the above implementation is that most classes do not support operator=(T const&)&&=delete -- ie, they do not block rvalues from being assigned to, which can lead to surprises if one of the types in the min does not . Fundamental types do.

Which is a side note: start deleting your rvalue reference operator=s people.

这篇关于实现可变最小/最大函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆