【问题标题】:What is the purpose of a scalar in the C++ type traits sense?C++ 类型特征意义上的标量的目的是什么?
【发布时间】:2016-09-02 22:08:27
【问题描述】:

我已经能够在 cppreference.com、cplusplus.com 和此站点 (What is a scalar Object in C++?) 上找到参考资料,这些资料使我能够确定特定 C++ 数据类型是否为标量。也就是说,我可以应用这样运行的心理算法:“它是引用类型、函数类型还是 void?如果不是,它是数组、类还是联合?如果不是,它是标量类型。”当然,在代码中,我可以申请std::is_scalar<T>。最后,我可以应用工作定义“标量类型是一种具有加法运算符的内置功能而没有重载(算术、指针、成员指针、枚举和 std::nullptr_t)的类型。”

我找不到的是对标量分类的用途的描述。为什么有人会关心某个东西是否是标量?这似乎是一种“剩余”分类,就像动物分类学中的“爬行动物”(“嗯,爬行动物是,嗯,一种羊膜动物,不是鸟类或哺乳动物”)。我猜它一定有一些用途来证明它的混乱。我可以理解为什么有人想知道一个类型是否是一个引用——例如,你不能引用一个引用。但是为什么人们会关心某个东西是否是标量呢?什么是标量?

【问题讨论】:

  • @JoachimPileborg:我认为 Alan 明白标量的含义,问题是:如果类型是标量,我会在我真正关心的地方编写什么(可能是模板化的)代码?我猜is_scalar 的存在是为了完整性,而不是出于任何激励用例,但也许其他人可以肯定地说。
  • 也许是为了编写更高效的模板代码:传递指向更复杂类型的指针可能比复制它们更快,或者您可能需要锁来实现对它们的原子操作,但是大多数架构,您可以将正确对齐的标量存储在寄存器中,并将其作为原子硬件指令读取或更新。
  • 具有类型特征的事情是它们不是孤立使用的,或者当某事物的类型已知时。它们主要用于模板元编程和 SFINAE,例如根据模板参数的类型选择特定的函数。如果您需要对“具有加法运算符的内置功能且没有重载的类型(算术、指针、成员指针、枚举和 std::nullptr_t)”的类型进行一些专门化,那么 std::is_scalar 是正确的函数使用。
  • @Lorehead,如果该评论是一个答案,我会投赞成票。
  • @Alan 好的,你去吧。

标签: c++


【解决方案1】:

给定is_scalar<T>,您可以确定operator=()、operator==() 和operator!=() 会为any em> T.

  • class T 可能有也可能没有这些,具有任意含义;
  • union T 有问题;
  • 函数没有=;
  • 引用可能包含其中任何一个;
  • 一个数组 - 对于两个不同大小的数组,== 和 != 将使其衰减为指针并进行比较,而 = 将在编译时失败。

因此,如果您有is_scalar<T>,则可以确保它们始终如一地工作。否则,您需要进一步了解。

【讨论】:

  • 您也可以确定,所有其他操作员都按照您的期望行事——+ - * / % & | ^ ~,对吧?
  • @lorro: "否则,您需要进一步观察。" 您可以先进一步观察,而不必理会is_scalar。毕竟,如果您使用 SFINAE 来确定一个类型是否可以进行== 比较,那么对于标量类型和其他类型一样。
  • @Zereges:有些是定义的,有些没有;例如,以上列表都没有跨算术类型和指针的共同含义。对于这些类型,您可以将! 定义为!=0,但基本上就是这样。
  • @NicolBolas: == 可能不会按照您对非标量类型的想法执行,即使从设计角度来看它是有效的。一个对象可能会存储其生命周期和当前值的日志,并比较 == 的当前值。使用== 并跨线程统一的简单缓存将统一具有不同历史记录的对象。您不会丢弃缓存;您只需通过比较(或统一)对其进行专门化。对于is_scalar<>,这可能是不必要的(例如,您可以安全地为此定义一个便利类型)。对于 = 来说更糟,它可能部分将 lhs 与 rhs 绑定到类。
  • @lorro:“== 对于非标量类型,即使从设计角度来看它是有效的,它也可能不会像你想的那样做。”是的,但这要么是用户提供了不适当的operator== 过载,或者您的设计错误,因为您使用operator== 作为您的功能的基础。但是几乎没有充分的理由将接口限制为only 的标量类型。您看不到无序容器无视 operator== 只是因为它可能无法执行您想要的操作;他们只是为您提供了一种方法,可以为他们提供替代方案。
【解决方案2】:

一个目的是编写更有效的模板特化。在许多架构上,传递指向对象的指针比复制它们更有效,但标量可以放入寄存器并使用单个机器指令进行复制。或者泛型类型可能需要锁定,而机器保证它将使用单个原子指令读取或更新正确对齐的标量。

【讨论】:

  • 我认为这是最有可能的答案。 Abrahams 和 Gurtovoy 的“C++ 模板元编程”(第 63 页)支持您的回答:“......一个通用函数应该通过引用接受参数。也就是说,通过引用传递任何像标量类型这样微不足道的东西通常是浪费的:on一些编译器, 标量在寄存器中通过值传递, 但是当通过引用传递时, 它们被强制到堆栈上. 需要的是一个元函数 param_type, 当它是一个标量时返回 T, 否则返回 T const & . "
  • @Alan:然而,每次调用像emplace 这样的可变参数函数时,都会通过引用传递标量。
  • 这个决定不是基于T 的大小,而不是标量吗?
  • @GManNickG:不一定是T 的大小。是否可以通过寄存器传递类型取决于几个因素。例如,Itanium ABI 禁止通过寄存器传递类型,如果该类型不是可简单复制的。关键是,虽然尺寸是此类事物的必要组成部分,但仅尺寸不足以保证任何事情。
【解决方案3】:

在 cppreference.com 上的注释中找到线索?

C++ 内存模型中的每个单独的内存位置,包括语言特征使用的隐藏内存位置(例如虚拟表指针),都具有标量类型(或者是非零长度的相邻位字段序列)。 表达式求值、线程间同步和依赖排序中的副作用排序都是根据单个标量对象定义的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-04
    • 2019-09-24
    • 1970-01-01
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多