C ++不同的minmax实现

时间:2018-09-26 07:48:19

标签: c++ c++11 stl

您可能(不知道)将std::minmax与自动和临时参数一起使用可能很危险。例如以下代码是UB,因为std::minmax返回一对引用,而不是值:

auto fun(){
    auto res = std::minmax(3, 4);
    return res.first;
}    

我想问问是否有可能使std::minmax函数安全地运行,或者至少在没有任何开销的情况下更安全?我想出了一个这样的解决方案,但是我不确定它是否等同于当前的minmax,因为对于类似于stl的实现和我的,生成的程序集是不同的。所以问题是:与类似minmax的问题相比,我std的实现可能存在哪些问题/缺点:

//below is std-like minmax
template< class T > 
constexpr std::pair<const T&,const T&> std_minmax( const T& a, const T& b ){
    return (b < a) ? std::pair<const T&, const T&>(b, a)
            : std::pair<const T&, const T&>(a, b);
}

//below is my minmax implementation
template< class T > 
constexpr std::pair<T, T> my_minmax( T&& a, T&& b ){
    return (b < a) ? std::pair<T, T>(std::forward<T>(b), std::forward<T>(a))
            : std::pair<T, T>(std::forward<T>(a), std::forward<T>(b));
}

Live demo at godbolt.org


正如你们中的一些人声称不清楚我要问的是什么,我想改写一下我想要的东西。我想编写与std::minmax完全一样的函数,但是如果给定一个临时值,则返回std::pair<T, T>而不是std::pair<const T &, const T &>。其次,在执行此操作时,我希望避免任何不必要的移动,数据复制等。

3 个答案:

答案 0 :(得分:2)

一种解决方案是,当T是右值引用时,然后复制它而不是返回右值引用:

#include <utility>

template<class T>
std::pair<T, T> minmax(T&& a, T&& b) {
    if(a < b)
        return {a, b};
    return {b, a};
}

当参数是右值引用时,T被推导为非引用类型:

int main() {
    int a = 1;
    int const b = 2;
    minmax(1, 1); // std::pair<int, int>
    minmax(a, a); // std::pair<int&, int&>
    minmax(b, b); // std::pair<const int&, const int&>
}

答案 1 :(得分:2)

我不确定您要达到什么目标。您写道:

  

没有任何开销

,但是您的解决方案将复制左值参数。是你想要的吗?

无论如何,您不能以这种方式使用具有相同模板参数的两个转发引用,因为如果两个函数参数具有不同的类别,它将失败:

template <typename T> void f(T&& a, T&& b) { }

int main() {
  int a = 3;
  f(a, 1);  // error: template argument deduction/substitution failed
}

对于第一个函数参数,将推论Tint&,第二个推论为int


如果您要删除任何副本,则唯一的可能是结果pair的成员为:

  1. 一个(常量)左值引用,如果它是一个左值

  2. (如果它是 rvalue )从该参数移出。

我认为这不可能实现。考虑:

std::string a("hello");
auto p = minmax(a, std::string("world"));

在这里,结果类型为std::pair<std::string&, std::string>。但是,如果是

auto p = minmax(a, std::string("earth"));

结果类型将不同,即std::pair<std::string, std::string&>

因此,结果类型将取决于运行时条件(通常需要运行时多态性)。


更新

出于好奇,我只想出一个包装器,该包装器可以通过(常量)指针来容纳某些对象:

template <typename T>
class val_or_ptr {
   std::variant<T, const T*> v_;
public:
   val_or_ptr(const T& arg) : v_(&arg) { }
   val_or_ptr(T&& arg) : v_(std::move(arg)) { }
   const T& get() const { return v_.index() ? *std::get<const T*>(v_) : std::get<T>(v_); }
};

这样,您可以将minmax定义为:

template <typename T, typename U,
          typename V = std::enable_if_t<std::is_same_v<std::decay_t<T>, std::decay_t<U>>, std::decay_t<T>>>
std::pair<val_or_ptr<V>, val_or_ptr<V>> minmax(T&& a, U&& b) {
   if (b < a) return { std::forward<U>(b), std::forward<T>(a) };
   else return { std::forward<T>(a), std::forward<U>(b) };
}

实时演示在这里:https://wandbox.org/permlink/N3kdI4hzllBGFWVH

这是非常基本的实现,但应防止同时复制minmax的左值和右值参数。

答案 2 :(得分:1)

使用C++17可以使用constexpr if绑定左值参数并复制其他所有内容。使用C++11之前,对于这样一个简单的用例,我可能会三思而后行,以令人恐惧的外观构建尖括号。

godboltcoliru

template <typename T>
decltype(auto) minmax(T&& x, T&& y)
{
    if constexpr(std::is_lvalue_reference_v<decltype(x)>)
        return std::minmax(std::forward<T>(x), std::forward<T>(y));
    else {
        auto const res = std::minmax(x, y);
        return std::make_pair(res.first, res.second);
    }
}

要支持混合的l / r值,您可能需要两个模板参数,if / else中需要4种情况,并且需要std::cref(res.xxx)作为std :: make_pair的一部分参数。