您可能(不知道)将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));
}
正如你们中的一些人声称不清楚我要问的是什么,我想改写一下我想要的东西。我想编写与std::minmax
完全一样的函数,但是如果给定一个临时值,则返回std::pair<T, T>
而不是std::pair<const T &, const T &>
。其次,在执行此操作时,我希望避免任何不必要的移动,数据复制等。
答案 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
}
对于第一个函数参数,将推论T
为int&
,第二个推论为int
。
如果您要删除任何副本,则唯一的可能是结果pair
的成员为:
一个(常量)左值引用,如果它是一个左值,
值(如果它是 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
之前,对于这样一个简单的用例,我可能会三思而后行,以令人恐惧的外观构建尖括号。
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的一部分参数。