你好!我是Xavier Jouvenot,今天,我们将了解为什么C ++标准的std::clamp
和std::ranges::clamp
函数的设计不佳,以及我们如何简单地改进它。
自我晋升:
这是一些社交网络,您可以关注我并检查我作为程序员和作家的工作ð
Personal blog, Twitter, GitHub
STD :: Clamp and STD :: Ranges :: Clamp的问题
自C ++ 17以来,该函数std::clamp可以作为助手提供,可以将变量保存在函数调用过程中指定的2个变量定义的值范围内。在C ++ 20中,还将函数std::ranges::clamp添加到C ++标准中。
这些功能被定义为:
namespace std
{
template<class T>
constexpr const T& clamp( const T& v, const T& lo, const T& hi );
template<class T, class Compare>
constexpr const T& clamp( const T& v, const T& lo, const T& hi, Compare comp );
namespace ranges
{
template< class T, class Proj = std::identity,
std::indirect_strict_weak_order<std::projected<const T*, Proj>> Comp =
ranges::less >
constexpr const T&
clamp( const T& v, const T& lo, const T& hi, Comp comp = {}, Proj proj = {} );
} // namespace ranges
} // namespace std
使用这些功能的代码看起来像:
auto value = std::clamp (variable, 0, 100);
auto other_value = std::ranges::clamp (other_variable, -100, 0);
,但它看起来也可以像以下代码:
auto value = std::clamp (50, 100, 75);
在这种情况下,很难猜测开发人员编写此代码的意图是什么。
实际上,由于代码中的错误,该范围的最小值和最大可以逆转,即根据C ++标准,不确定的行为。或开发人员编写此行认为clamp
函数在要夹紧的值之前采用了范围。
无论原因的起源是什么,此代码在汇编过程中不会产生任何警告,当我们可以轻松地想象解决此类错误的解决方案时不会被忽略。
改进可能
为了使开发人员的生活更轻松,一种解决方案可能是更改clamp
函数的设计:与clamp
范围相关的2个参数耦合。例如,我们可以使用std::pair夫妇夫妇:
#include <cassert>
#include <functional>
namespace mystd
{
template<class T, class Compare>
constexpr const T& clamp( const T& v, const std::pair<T, T>& range, Compare comp )
{
assert(comp(range.first, range.second));
return comp(v, range.first) ? range.first : comp(range.second, v) ? range.second : v;
}
template<class T>
constexpr const T& clamp( const T& v, const std::pair<T, T>& range )
{
return clamp(v, range, std::less<T>{});
}
}
这样,当调用函数clamp
时,开发人员必须使其显而易见夹具的值和范围是什么:
auto v = mystd::clamp (5, {7, 10});
此外,如果范围的最小值和最大值逆转,将触发断言。
/app/example.cpp:10: constexpr const T& mystd::clamp(const T&, const std::pair<_FIter, _FIter>&, Compare) [with T = int; Compare = std::less<int>]: Assertion `comp(range.first, range.second)' failed.
我们还可以通过使用结构而不是std::pair
来使断言更加明确。的确,我们将命名为.first
元素和.second
元素,而是将min
和max
属性命名。这样,以下代码:
namespace mystd2
{
template<class T>
struct clamp_range
{
T min, max;
};
template<class T, class Compare>
constexpr const T& clamp( const T& v, const clamp_range<T>& range, Compare comp )
{
assert(comp(range.min, range.max));
return comp(v, range.min) ? range.min : comp(range.max, v) ? range.max : v;
}
template<class T>
constexpr const T& clamp( const T& v, const clamp_range<T>& range )
{
return clamp(v, range, std::less<T>{});
}
}
int main()
{
auto v = mystd2::clamp (5, {10, 7});
}
将为我们提供以下断言:
/app/example.cpp:32: constexpr const T& mystd2::clamp(const T&, const mystd2::clamp_range<T>&, Compare) [with T = int; Compare = std::less<int>]: Assertion `comp(range.min, range.max)' failed.
,使开发人员在遇到问题时更容易调试问题。
结论
本文的标题对某些人来说可能有点点击诱饵,毫无疑问,其他一些将会说明这里描述的解决方案可能会得到改进(例如,概念),这可能是真的!但是,结论保持不变,标准函数std::clamp
可以并且应该得到改进,以帮助开发人员避免避免的错误。
如果您想尝试本文中描述的解决方案,我会让您获得godbolt link,如果您对如何改善std::clamp
功能有任何想法,请随时在本文下写一点评论。
谢谢大家阅读本文,
直到我的下一篇文章,祝您有美好的一天ð