我可以以某种方式优雅地禁止在我的模板函数中使用未签名的变量吗?

Can I somehow elegantly forbid using unsingned variables in my template function?

考虑这段代码:

template <typename T>
T abs(const T& n)
{
    if (!std::is_signed<T>::value)
        throw logic_error;
    if (n < 0)
        return -n;
    return n;
}

我想完全禁止我的函数与 unsigned 变量一起使用,因为这没有意义,而且用户可能甚至不知道他使用了 unsigned 变量.例如,我可以稍微避免这个问题:

template <typename T>
T abs(const T& n)
{
    if constexpr(!std::is_signed<T>::value)
        n += "";
    if (n < 0)
        return -n;
    return n;
} 

但是如果我调用abs(4u),编译错误不是很明显。类似于 "can't apply += const char[1] to double"。我能以某种方式让它更明显吗?或者只是进行多次重载?

#include <type_traits>

template <typename T>
T abs(T n)
{
  static_assert(std::is_signed<T>::value, "Type must be signed.");
  if (n < 0) { return -n; }
  return n;
}

int main()
{
    return abs(3u);
}

1。概念

从 C++20 开始,您可以为此使用概念。

如果您只接受整数参数,您可以使用 std::signed_integral:

#include <concepts>

template <std::signed_integral T>
T abs(const T& n)
{
    if (n < 0)
        return -n;
    return n;
}

如果您还想允许 double,等等...您必须制定自己的概念:

template<class T>
concept signed_value = std::is_signed_v<T>;

template <signed_value T>
T abs(const T& n)
{
    if (n < 0)
        return -n;
    return n;
}

如果您尝试使用无符号类型,将导致这样的编译器错误:

error: no matching function for call to 'abs(unsigned int&)'
note: candidate: 'template<class T>  requires  signed_integral<T> T abs(const T&)'

这很清楚 abs() 只接受来自签名的签名类型。


2。 SFINAE

如果 C++20 不可用,您可以使用 SFINAE,尽管您收到的错误消息非常含糊:

template <class T>
std::enable_if_t<std::is_signed_v<T>, T> abs(const T& n)
{
    std::cout << "HELLO" << std::endl;
    if (n < 0)
        return -n;
    return n;
}

但这会导致出现如下错误消息:

error: no matching function for call to 'abs(unsigned int&)'
note: candidate: 'template<class T> std::enable_if_t<is_signed_v<T>, T> abs(const T&)'
note:   template argument deduction/substitution failed:
    In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = unsigned int]':
    required by substitution of 'template<class T> std::enable_if_t<is_signed_v<T>, T> abs2(const T&) [with T = unsigned int]'

因此,您需要熟悉这方面的 SFINAE 错误。


3。好老static_assert

或者,您也可以在函数中添加 static_assert
这在声明中根本不可见,仅在函数定义中可见。

template <class T>
T abs(const T& n)
{
    static_assert(std::is_signed_v<T>, "T must be signed!");
    if (n < 0)
        return -n;
    return n;
}

4。使用 std::abs()

C++ 已经提供了一个 std::abs() 实现来为您处理所有潜在的边缘情况,所以如果可以的话,我建议您改用那个。

如果您为给定的 int 类型传递最小值,您的代码也会包含一个潜在的错误,例如INT_MIN.

  • 一个32位的int可以表示的最小值是-2147483648
  • 但它能表示的最大也只有2147483647

因此,使用最小值调用 abs() 函数,例如abs(-2147483648) 会导致 未定义的行为std::abs() 具有相同的行为,但仍然值得指出)。