查找C++模板标识符列表的末尾



假设出于解析目的,想要跳过模板标识符列表的内容:

template<(valid code)>
        ^            ^
        | from       | to
首先想到的是

盲目地找到第一个>,但这并不总是有效:

template<(valid code),template<(valid code)> >
        ^                                  ^
        | from                             | to, oops

更好的方法是跳过成对的<并以递归方式>:

template<(valid code),template<(valid code)> >
        ^                                    ^
        | from                               | to, better

然而,即使是这种方法对于像这样晦涩但有效的杰作(来自bits\random.h,第69行,GCC 4.7.x)也失败了:

template<(...),bool = __w < static_cast<size_t>(...)>
        ^                 ^            ^      ^     ^     ^
        | 1               | 2          | 3    | 2   | 1   | where did 0 go?

那么我的问题是,找到任何有效模板标识符列表末尾的正确方法是什么?

真的没有简单的方法可以找到任何有效模板标识符列表的末尾,因为比 bits/random.h 中的示例有更多的病理可能性。(在这个答案的末尾有一个病理案例的例子:C++是上下文无关的还是上下文相关的?其中标识符是否是模板取决于一个 largish 整数是否是素数。

该算法很容易说明(在 C++11 标准第 14.2 条第 3 款中,[temp.names] )。基本上,<是模板括号,当且仅当它跟随模板标识符、单词 templatecast运算符之一时。一旦你有一个开放的<,它就会与第一个>匹配,它没有嵌套在某些括号语法(包括大括号和括号)中,并且不匹配一些嵌套的模板括号。在 C++11 中,这包括属于>>令牌一部分的>

例如,如果random.h的例子说bool = __w > ...>将被视为结束模板括号。但由于它是<,并且__w不是模板标识符,因此它被视为小于运算符。恕我直言,好的风格总是将比较和移位运算符括在括号中,如果它们在模板括号内,但这只是我。

标准的确切措辞:

在名称查找发现名称是模板名称运算符函数 ID 或文本运算符 ID 引用一组重载函数后,如果后跟一个<,则其成员是函数模板,则<始终被视为模板参数列表的分隔符,而不是小于运算符。解析模板参数列表时,第一个非嵌套>将作为结束分隔符,而不是大于运算符。

该算法很难实现,因为没有简单的方法可以知道哪些标识符是模板标识符,因为您需要解析整个程序(至少在使用点之前)才能知道每个标识符是什么样的东西。这包括查找和解析包含的文件,以及以其他方式预处理源代码。

如果你真的希望它是准确的,你需要使用C++解析器,并意识到它可能是一个非常缓慢的过程,因为它可以包括模板实例化(如引用的素数检查示例)。如果你只是想做一些类似语法着色的事情,你可能会得到一个近似值。

您需要

跳过()括号内的任何内容。
您需要在<之前查看标识符,并知道它是否是类型名称。

第二个是有问题的,您需要扫描所有源代码并识别 class/struct/union s 和 typedef s 的所有名称,以便当您到达形式 __w < __a 的表达式(在本例中简化)时,您知道__w是否命名类型。
这有问题的原因是,如果你遇到任何预处理器元编程(比如Boost关于这个主题的库),你基本上只能编写一个程序来评估这些程序,看看创建了哪些类型名称。
此外,考虑这段代码(比显示它有多难的必要要复杂一些):

    template <template <class> class T>
    struct Base
    {
        template <class X>
        using type = T<X>;
    };
    template <>
    struct Base<std::numeric_limits>//Let's assume numeric_limits only has one template argument for this example
    {
        static const int type = 0;
    };
    template <class T, Base<T>::type < 0>
    struct OtherClass{};

这种复杂性是 typename 关键字的用途,因为Base<T>是一个依赖范围,因此您无法立即判断Base<T>::type命名类型还是成员变量。 通过这种方式,语法要求Base<T>::type是静态成员变量,typename Base<T>::type是类型(我们不知道哪种类型,但没关系,我们只是试图找到模板标识符列表的末尾)。
除了上面的示例之外,您还需要处理鲜为人知的 template 关键字用法。 由于Base<T>是一个依赖范围,如何解析Base<T>::type<0>? 这取决于type是什么。 在这种情况下,语法要求Base<T>::type是成员变量并解析为(Base<T>::type < 0) >Base<T>::template type<0>是模板表达式。

关键字 typenametemplate ,虽然在您了解依赖范围之前很难理解您的思想,但最终会让您的工作更轻松。 否则,如果不编写完整的编译器,模板标识符列表在哪里结束的简单问题几乎不可能回答(即使这样,编译器也很难编写)。

撇开依赖作用域

不谈,您仍然需要能够处理非依赖作用域。 这意味着您需要知道Base<numeric_limits>::type是否是一种类型,这意味着扫描每个struct/class/union以查找typedef,并解析基类 typedef s 和私有继承 + using 语句的公共继承。

如果你将自己限制在编译的代码上,你的工作仍然是容易处理的,否则你还有很多工作要做。

我不保证这就是一切,只是这很可能会让你忙一段时间。

最新更新