【问题标题】:Is there a constexpr ordering of types in C++?C++ 中的类型有 constexpr 排序吗?
【发布时间】:2014-07-05 19:40:13
【问题描述】:

C++ 是否提供所有类型集合的排序作为常量表达式?哪个特定顺序都没有关系,任何人都会这样做。这可以是 constexpr 比较函数的形式:

template <typename T1, typename T2>
constexpr bool TypeLesser ();

我的用途是编译时自平衡类型的二叉搜索树,作为 (cons/nil) 类型列表的替代品,以加快编译速度。例如,检查一个类型是否包含在这样的树中可能比检查它是否包含在类型列表中更快。

如果标准 C++ 不提供此类功能,我也将接受编译器特定的内在函数。

请注意,如果获得排序的唯一方法是通过在整个代码库(包括许多模板和匿名结构)中添加样板来手动定义它,我宁愿保留类型列表。

【问题讨论】:

  • 比较typeid的可能吗?
  • @πάνταῥεῖ 对于我的问题,std::type_info 似乎没有任何用处。请注意,我正在寻找 constexpr 排序。
  • 我认为严格的弱排序对于 BST 来说是不够的。我在想sizeof(T)...
  • type_index,但不是constexpr
  • @Praetorian:由于所有类型的总和取决于查看所有翻译单元,因此似乎不太可能有 constexpr 排序。至少没有特殊的编译器和链接器支持。

标签: c++ templates binary-search-tree template-meta-programming


【解决方案1】:

该标准的唯一排序是通过type_info(由typeid 表达式提供),您可以通过type_index 更轻松地使用它——后者提供了普通的比较功能,因此可以在集合中使用。

我猜它的祖先是 Andrei Alexandrescu 在“现代 C++ 设计”中的课程。

现在不是编译时间。


为了减少编译时间,您可以为有问题的类型定义 traits classes,为每种类型分配一些序数值。 128 位 UUID 可以很好地作为类型 ID,以避免保证唯一 ID 的实际问题。这当然假设您或客户端代码控制可能的类型集。

在早期的 Boost 机制中使用过必须“注册”相关类型的想法来确定函数结果类型。


无论如何我必须认真地推荐测量编译性能。在运行时很快的平衡操作,只涉及调整几个指针,在编译时可能很慢,涉及创建一个全新类型的巨大描述符。因此,即使检查类型集成员可能会更快,但构建类型集可能会慢得多,例如O(n2).

免责声明:我没有尝试过。

但无论如何,我再次记得 Andrei Alexandrescu 在已经提到的“现代 C++ 设计”中讨论过类似的东西,或者如果你没有访问那本书的权限,请查看 Loki 库(这是一个那本书里的东西)。

【讨论】:

    【解决方案2】:

    你有两个主要问题:1)你没有具体的比较标准(所以这个问题,不是吗?),2)你没有在编译时进行排序的任何标准方式。

    第一次使用std::type_info 就像其他人建议的那样(它目前通过std::type_index 包装器在地图上使用)或定义您自己的元函数来指定不同类型的排序标准。其次,您可以尝试编写自己的基于模板元编程的快速排序算法。这就是我为我的个人元编程库所做的,并且完美运行。

    关于假设“自平衡搜索树应该比经典类型列表表现更好”我真的鼓励你在说之前做一些分析(试试templight)。 编译时性能与经典运行时性能无关,很大程度上取决于编译器具有的模板实例化系统的确切实现
    例如,根据我自己的经验,我很确定我的简单“O(n)”线性搜索可以比你的自平衡树执行得更好。为什么? 记忆。编译时性能不仅仅是实例化深度。事实上,记忆在这方面起着至关重要的作用。

    给你一个真实的例子:考虑快速排序的实现(伪元代码):

    list sort( List l )
    {
        Int pivot = l[l.length/2];
    
        Tuple(List,List) lists = reorder( l , pivot , l.length/2 );
    
        return concat( sort( lists.left ) , sort( lists.right ) );
    }
    

    我希望这个例子是不言自明的。注意它的工作方式,没有副作用。如果有一天 C++ 中的元编程有这种语法,我会很高兴...

    这就是快速排序的递归情况。由于我们使用的是类型列表(在我的例子中是可变类型列表),因此计算枢轴值的第一个元指令具有 O(n) 复杂度。具体需要 N/2 的模板实例化深度。秒步骤(重新排序)可以在 O(n) 中完成,并且连接是 O(1)(请记住,这是 C++11 可变参数类型列表)。

    现在考虑一个执行示例:

    [1,2,3,4,5]

    第一步调用递归case,所以trace是:

    • Int pivot = l[l.length/2]; 遍历列表直到 3。这意味着 执行遍历 [1][1,2][1,2,3] 所需的实例被记忆。

    • 在重新排序期间,会生成更多的子遍历(以及由元素“交换”生成的子遍历的组合)。

    • 递归“调用”和连接。

    由于执行到列表中间的这种线性遍历是被记忆的,它们在整个排序执行过程中只被实例化一次。当我第一次使用 templight 遇到这个时,我完全傻眼了。事实上,查看实例化图,只有第一个大遍历被实例化,小遍历只是大遍历的一部分,并且由于记住了大遍历,所以小遍历不会再次实例化

    哇,编译器至少能够记住如此缓慢的线性遍历的一半,对吧?但是,如此巨大的记忆工作的成本是多少?

    我想说的是:在进行模板元编程时,忘记关于运行时性能、优化、成本等的一切,不要做假设。衡量。 你正在进入一个完全不同的联盟。我不完全确定哪种实现(您的自平衡树与简单的线性遍历)更快,因为这取决于编译器。我的例子只是为了展示一个编译器实际上是如何完全打破你的假设的。

    旁注:我第一次做这些分析时,我把它们展示给了我大学的一位算法老师,他仍在试图弄清楚发生了什么。其实他在这里问了一个关于如何衡量这个怪物的复杂度和性能的问题:Best practices for measuring the run-time complexity of a piece of code

    【讨论】:

    • 感谢您提供的信息,但我实际上并没有要求详细解释编译时间与运行时性能。当然,我打算进行简介。但如果没有比较功能,我就无法做到这一点。而且您还没有提供这样做的方法(type_info 不是编译时间)。由于涉及的不同类型(包括模板类和匿名类)的数量非常多,因此定义自定义比较是不可能的,我怀疑这些类型在代码周围都需要样板。
    猜你喜欢
    • 1970-01-01
    • 2010-09-16
    • 2022-10-25
    • 2022-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-14
    相关资源
    最近更新 更多