夹具功能设计不佳
#编程 #c #cpp #调试

你好!我是Xavier Jouvenot,今天,我们将了解为什么C ++标准的std::clampstd::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元素,而是将minmax属性命名。这样,以下代码:

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功能有任何想法,请随时在本文下写一点评论。


谢谢大家阅读本文,
直到我的下一篇文章,祝您有美好的一天ð