C++ 不同的 minmax 实现
Posted
技术标签:
【中文标题】C++ 不同的 minmax 实现【英文标题】:C++ different minmax implementation 【发布时间】:2018-09-26 07:48:18 【问题描述】:您可能(不)知道将std::minmax
与自动和临时参数一起使用可能很危险。例如下面的代码是 UB,因为 std::minmax
返回一对引用,而不是值:
auto fun()
auto res = std::minmax(3, 4);
return res.first;
我想问一下是否有可能使std::minmax
函数安全地运行,或者至少更安全而没有任何开销?我想出了一个这样的解决方案,但我不完全确定它是否等同于当前的minmax
,因为生成的程序集对于类似 stl 的实现和我的不同。所以问题是:与std
相关的minmax
的实现可能存在哪些问题/缺点:
//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 &>
。其次,在执行此操作时,我希望避免任何不必要的移动、复制数据等。
【问题讨论】:
非常量引用更喜欢第二次重载。混合左值/右值难以推断。可能还有更多。 @PasserBy 我不建议同时使用它们。我问的是第一个和第二个之间的意外行为或性能差异。混合 lval/rval 是真的,我怎么能减轻它? @DanielLangr:没有内联的minmax
与int
进行比较是没有意义的,因为它将被内联。
使用 2 个不同的模板参数,您会遇到返回值类型的问题(是的 std::common_type
可能会有所帮助,但也必须处理 constness,结果可能会令人惊讶/不直观)...
IIUC,您希望得到的 pair
成员为 1) 对相应函数参数的 (const) 左值引用,如果这是一个左值或 2) 从该参数移动的值,如果它是一个右值。这样对吗?只是为了澄清。
【参考方案1】:
我不确定您要达到什么目的。你写道:
没有任何开销
但您的解决方案将复制左值参数。是你想要的吗?
无论如何,您不能以这种方式使用具有相同模板参数的两个转发引用,因为如果两个函数参数具有不同的类别,它将失败:
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
的成员是:
a (const) lvalue reference 到相应的函数参数,如果它是 lvalue,
一个 value 从那个参数移动,如果它是一个 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&>
。
因此,生成的类型将取决于运行时条件(通常需要运行时多态性)。
更新
出于好奇,我刚刚想出了一个包装器,它可以通过 (const) pointer 或 value 来保存一些对象:
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】:一种解决方案是当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&>
【讨论】:
请注意,这不适用于混合右值/左值参数(T
的模板推导将失败)。这与 OP 的解决方案相同,但他/她要求更好的替代 std::minmax
,所以我想他/她实际上想要支持混合类别的参数。
@DanielLangr 它不支持设计混合的右值/左值参数。【参考方案3】:
使用C++17
可以使用constexpr if
绑定左值参数并复制其他所有内容。对于C++11
,我可能会在为这样一个简单的用例构建一个看起来很吓人的尖括号之前三思而后行。
godbolt, coliru
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 个 case,以及 std::cref(res.xxx)
作为 std::make_pair 的参数以表示部分。
【讨论】:
请注意,这不适用于混合右值/左值参数(T
的模板推导将失败)。这与 OP 的解决方案相同,但他/她要求更好的替代 std::minmax
,所以我想他/她实际上想要支持混合类别的参数。
@DanielLangr - 我在答案底部添加了一个建议以上是关于C++ 不同的 minmax 实现的主要内容,如果未能解决你的问题,请参考以下文章