如果您想生成有效 C++ 表达式的语法(可能作为所有 C++ 表达式的子集,就像 do-notation 将自身限制为 monad 操作一样),静态验证它们并使用它们,那么您最好的选择是Boost.Proto。简而言之,它本身就是一个 EDSL 来编写和描述 EDSL。
我不会详细介绍如何使用它。虽然它可能很难学会使用,特别是如果你不习惯 C++ 元编程,文档很棒,如果你曾经写过语法,我相信你会找到你的标记。在我的另一个答案中,我向某人介绍了如何使用仅接受简单算术表达式并消耗它们来计算它们的导数的语法编写 EDSL,因此您可能需要检查一下。
至于您的确切问题,恐怕答案必须是简短的“不,您不能那样做”,或者是长的“您可以在 Boost.Phoenix 显示的范围内做到这一点,但可能不是考虑到您的 EDSL 用户的隐秘错误和/或额外的编译时间,值得您花时间实施它”。我对此的推理是您想要做的事情适合两个级别:do-notation 是 Haskell 的特定功能,同时在 EDSL 本身的级别上使用语法树并为其赋予语义。
碰巧的是,典型的 Proto 风格的 EDSL 是有效的 C++ 表达式,并且该语言不提供该级别的范围,变量在单独的语句中声明。例如,_a + _b
是一个有效的 C++ 片段,因为_a
和_b
是由 Phoenix 提供的声明的 C++ 变量,但在 EDSL 中不是一个有效的程序,因为_a
和_b
未绑定。是的,错误将被捕获,但您必须自己实现。相比之下,do-notation 是 Haskell 的一部分,因此任何 EDSL 都免费继承它。也就是说,return (a + b)
它本身永远不会有效 - 需要有 somea
和 some b
。
不过有一些事情要记住。C++11 提供了 lambda 表达式,因此您实际上可以在这里获得一些作用域——但这些在 EDSL 中是不透明的,语法树只会显示一个变量。一些自省可能会揭示该变量对于某些类型是可调用的,但仅此而已。即使您要求 lambda 在 EDSL 中返回一个值,也不知道它们还能做什么。这并不总是值得担心,我会说它非常适合某些 EDSL。
类似地,C++11 使“分解” EDSL 表达式的部分变得更加容易。这并不等同于a <- foo
do-notation 的糖,但它等同于let a = foo
. 因此,您实际上可以毫不费力地使以下操作“正确”:
auto double_pop = make_tuple(pop(), pop());
auto program = (push(3), push(4), consume(double_pop));
这可能相当于单调和人为的以下内容:
program = do
let a = pop
return consume `ap` a `ap` a
(由于 Boost.Proto 最初是一个 C++03 库,因此请确保在使用 C++11 之前仔细阅读文档auto
,IIRC 有一个警告。)