3

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

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?

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

4

2 回答 2

2

确实没有简单的方法可以找到任何有效模板标识符列表的结尾,因为比来自bits/random.h. (在这个答案的末尾有一个病态案例的例子:C++ 是上下文无关的还是上下文敏感的?其中标识符是否是模板取决于较大的整数是否为素数。)

该算法很容易说明(它位于 C++11 标准第 14.2 节的第 3 段中[temp.names])。基本上,当且仅当它跟在模板标识符、单词或运算符之一之后,a<才是模板括号。一旦你有一个 open ,它就会被第一个匹配,它没有嵌套在一些括号语法(包括大括号和方括号)中,并且不匹配一些嵌套的模板括号。在 C++11 中,这包括哪些是令牌的一部分。templatecast<>>>>

例如,如果random.h示例说bool = __w > ...>将被视为关闭模板括号。但由于它是一个<并且__w不是模板标识符,因此它被视为小于运算符。恕我直言,如果比较和移位运算符位于模板括号内,则始终将它们包装在括号中,但这只是我。

标准的准确措辞:

在名称查找发现名称是模板名称operator-function-id文字 operator-id引用一组重载函数后,如果 this 后跟 a <,则其中任何成员都是函数模板,<则总是作为模板参数列表的分隔符,从不作为小于运算符。解析template-argument-list时,第一个非嵌套>的被视为结束分隔符,而不是大于运算符。

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

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

于 2013-11-10T16:12:13.700 回答
1

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

这第二个是有问题的,您需要扫描所有源代码并识别class/struct/unions 和typedefs 的所有名称,以便当您到达表单的表达式__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>是模板表达式。

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

除了依赖范围之外,您仍然需要能够处理非依赖范围。这意味着您需要知道是否Base<numeric_limits>::type是类型,这意味着扫描每个struct/class/unionfortypedef并解析基类typedef的公共继承和私有继承 +using语句。

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

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

于 2013-11-10T16:12:54.160 回答