【问题标题】:How can I force template parameter type to be signed?如何强制对模板参数类型进行签名?
【发布时间】:2017-11-17 15:29:21
【问题描述】:

我将使用以下示例来说明我的问题:

template<typename T>
T diff(T a, T b)
{
  return a-b;
}

我希望这个模板函数只在类型 T 被签名时才有效。我能想到的唯一解决方案是对所有无符号类型使用 delete 关键字:

template<>
unsigned char diff(unsigned char,unsigned char) == delete;
template<>
unsigned char diff(unsigned char,unsigned char) == delete;

还有其他解决方案吗?

【问题讨论】:

  • std::is_unsigned 可以帮助你(或is_signed)。还没想好具体怎么写,不过是某种 SFINAE。

标签: c++ c++11 templates c++14


【解决方案1】:

您可以将std::is_signedstd::enable_if 一起使用:

template<typename T>
T diff(T a, T b);

template<typename T>
std::enable_if_t<std::is_signed<T>::value, T> diff(T a, T b) {
    return a - b;
}

这里std::is_signed&lt;T&gt;::valuetrue当且仅当T被签名(顺便说一句,浮点类型也是true,如果你不需要它,考虑与std::is_integral结合) .

std::enable_if_t&lt;Test, Type&gt;std::enable_if&lt;Test, Type&gt;::type 相同。 std::enable_if&lt;Test, Type&gt; 被定义为空结构,以防Test 为假,否则定义为只有 typedef type 等于模板参数Type 的结构。

所以,对于有符号类型,std::enable_if_t&lt;std::is_signed&lt;T&gt;::value, T&gt; 等于 T,而对于无符号,它没有定义,编译器使用 SFINAE 规则,因此,如果您需要为特定的无符号类型指定实现,您可以轻松做到这一点:

template<>
unsigned diff(unsigned, unsigned)
{
    return 0u;
}

一些相关链接:enable_ifis_signed

【讨论】:

  • 在这种情况下我不会使用enable_if,因为它在编译时没有提供有用的信息。只会告诉你没有找到这个符号……在这种特殊的环境中并不酷。
  • 我认为如果不是专门化 template 而只是重载一个函数,代码会更简洁。也就是说,您不需要最初的 template 声明,unsigned 的特化只是成为重载(不需要“template&lt;&gt;”)。
  • @misterwhy,同意static_assert 在这里更可取
【解决方案2】:

static assertstd::is_signed 怎么样?

template<typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed<T>::value, "signed values only");
    return a-b;
}

现场观看: http://ideone.com/l8nWYQ

【讨论】:

  • 很好的答案,但请注意 static_assert 将在 C++17 中添加没有消息,并且此问题使用 C++11 和 C++14 进行标记。
  • @December 那么只添加一条消息会有什么问题?
  • @Spencer 不多,而且我认为这不会干扰 Louen 的意图。
  • 我几乎总是更喜欢static_assert 而不是 SFINAE,因为它会产生更好的错误消息。 OTOH,static_assert 表示您将来不能 SFINAE 关闭该功能。至少,我认为大部分公共API函数应该是static_asserts
  • 是的,很抱歉没有发送消息static_assert。感谢编辑!
【解决方案3】:

我会使用 static_assert 并带有很好的错误消息。 enable_if 只会让您的 IDE 陷入困境,并且无法编译并显示类似

的消息

找不到标识符diff

这没有多大帮助。

那么为什么不这样呢:

#include <type_traits>

template <typename T>
T diff(T a, T b)
{
    static_assert(std::is_signed< T >::value, "T should be signed");
    return a - b;
}

这样,当您使用非签名类型调用 diff 时,您将让编译器编写此类消息:

错误:T 应该被签名

调用diff 的位置和值,这正是您要查找的内容。

【讨论】:

  • 为什么enable_if让IDE陷入困境
  • 麻烦是一个强词;我的真正意思是,你的 IDE 不能阻止你输入 diff(1, 2u),但是由于 enable_if,它会使函数消失,从而突出一个错误而不告诉你哪里错了。
【解决方案4】:

作为另一种选择,您可能会添加带有 std::is_signed 类型特征的 static_assert

template<typename T>
auto diff(T x, T y)
{
    static_assert(std::is_signed<T>::value, "Does not work for unsigned");
    return x - y;
}

这样:

auto x = diff(4, 2); // works
auto x = diff(4U, 2U); // does not work

【讨论】:

    【解决方案5】:

    所以我对你的函数有一些问题。

    首先,您的函数需要匹配所有 3 种类型——左、右和结果类型。所以signed char a; int b; diff(a-b); 不会无缘无故地工作。

    template<class L, class R>
    auto diff( L l, R r )
    -> typename std::enable_if<
      std::is_signed<L>::value && std::is_signed<R>::value,
      typename std::decay<decltype( l-r )>::type
    >::type
    {
      return l-r;
    }
    

    我想做的第二件事是创建一个 diff 对象;你不能轻易地传递你的 diff 函数,高阶函数很棒。

    struct diff_t {
      template<class L, class R>
      auto operator()(L l, R r)const
      -> decltype( diff(l,r) )
      { return diff(l,r); }
    };
    

    现在我们可以将diff_t{} 传递给算法,因为它在一个(普通)C++ 对象中保存了diff 的“重载集”。

    现在这是严重的矫枉过正。一个简单的static_assert 也可以工作。

    static_assert 将生成更好的错误消息,但不支持使用 SFINAE 来查看是否可以调用 diff 的其他代码。它只会产生一个硬错误。

    【讨论】:

      【解决方案6】:

      您的程序期望结果是什么?就目前而言,您返回一个无符号作为差异的结果。恕我直言,这是一个等待发生的错误。

      #include <type_trait>
      
      template<typename T>
      auto diff(T&& a, T&& b)
      {
          static_assert (std::is_unsigned<T>::value);
          return typename std::make_signed<T>::type(a - b);
      }
      

      一个更现代的等待写这个:

      inline auto diff(const auto a, const auto b)
      {
          static_assert (   std::is_unsigned<decltype(a)>::value 
                         && std::is_unsigned<decltype(b)>::value );
          return typename std::make_signed<decltype(a -b)>::type(a - b);
      }
      

      [编辑] 我觉得有必要添加此评论:在数学方程式中使用无符号整数类型总是很棘手。上面的例子对于任何数学包来说都是一个非常有用的附加组件,如果在现实生活中,你经常不得不求助于转换来产生差异signed,或者数学不起作用。

      【讨论】:

      • 您的“更现代的等待”使用什么专有的编译器扩展?那不是标准 C++。函数(不包括 lambda)不能有 auto 类型的参数。因此,IMO 不是一个好的建议。
      • 标准 ISO c++17.
      • 那么你就可以毫无问题地向我展示一份旅行报告等该论文被接受的地方。我找不到任何证据表明它是:只注意到它是一个或多个概念提案的一部分。在这种情况下,不,它不是 C++17。
      • 编译器允许的不是证据。 (并且生成的程序集更无关紧要。)所以我再问一次:你有关于标准所说的内容的链接吗?这才是最重要的。编译器允许的内容很少说明标准允许的内容(最终案例:UB)。 IIRC,其中一些甚至在“标准”模式下也启用了一些非标准扩展,除非给出-Wpedantic 或等效的。再说一次,我不记得有任何迹象表明 auto 函数参数已进入 C++17,甚至是非草案 TS。
      • ...哦,看看那个:warning: ISO C++ forbids use of 'auto' in parameter declaration [-Wpedantic]
      【解决方案7】:
      #include <type_traits>
      template<typename T>
      std::enable_if_t<(0>-T(1)),T> diff(T a, T b)
      {
        return a-b;
      }
      

      使用 (0>-T(1)) ,我假设 -1 的 init T 将小于 0。并且无符号值不可能小于 0

      【讨论】:

      • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
      • 尤其是当代码以如此密集、不可读的格式编写时。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-17
      • 1970-01-01
      相关资源
      最近更新 更多