假设出于解析目的,想要跳过模板标识符列表的内容:
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]
)。基本上,<
是模板括号,当且仅当它跟随模板标识符、单词 template
或cast
运算符之一时。一旦你有一个开放的<
,它就会与第一个>
匹配,它没有嵌套在某些括号语法(包括大括号和括号)中,并且不匹配一些嵌套的模板括号。在 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>
是模板表达式。
关键字 typename
和 template
,虽然在您了解依赖范围之前很难理解您的思想,但最终会让您的工作更轻松。 否则,如果不编写完整的编译器,模板标识符列表在哪里结束的简单问题几乎不可能回答(即使这样,编译器也很难编写)。
不谈,您仍然需要能够处理非依赖作用域。 这意味着您需要知道Base<numeric_limits>::type
是否是一种类型,这意味着扫描每个struct/class/union
以查找typedef
,并解析基类 typedef
s 和私有继承 + using
语句的公共继承。
如果你将自己限制在编译的代码上,你的工作仍然是容易处理的,否则你还有很多工作要做。
我不保证这就是一切,只是这很可能会让你忙一段时间。