我使用 Bison 与 C++ 和变体选项来构建 AST。如果我理解正确的话,在我使用 $$ = ...
为它们分配某些内容之前,Bison 总是默认构造语义值(在我的例子中是 AST 节点)。但是,我想保留一些(实际上是大多数)AST 节点的非默认可构造性,因为这会使它们处于无效状态。如果我允许每个节点的默认构造,我就必须依赖运行时检查来确保我正在使用的每个节点都是有效的。有没有办法让 Bison 不默认构造我的节点,而只在我执行 $$ = ...
操作时构造它们?
编辑:@Some 程序员请求一个示例,所以就在这里,但它很长,对此感到抱歉。
假设以下 AST 结构:
namespace node {
template <typename T>
struct component {
// Not default constructible.
component (T &&);
component (component &&);
component & operator = (T &&);
component & operator = (component &&);
~component ();
T & get ();
T const & get () const;
private:
T * ptr;
};
template <typename T>
struct optional_component {
// Default constructible:
optional_component (): ptr{nullptr} {}
optional_component (T &&);
optional_component (component &&);
optional_component & operator = (T &&);
optional_component & operator = (component &&);
~optional_component ();
T & get ();
T const & get () const;
// Need to check for present value before use:
bool has_value () const { return ptr; }
private:
T * ptr;
};
struct C {
int val;
};
struct A {
// Not default constructible, because component<C> is not.
component<C> bar;
};
struct B {
// Default constructible, because component<C> is too.
optional_component<C> c;
}
}
namespace category {
struct X {
// Not default constructible,
// unless the first type of the variant is default constructible,
// or I provide the default constructor manually.
std::variant<node::A, node::B> node;
};
}
以及以下解析器定义:
%language "c++"
%define api.value.type variant
%define api.value.automove
%parse-param { category::X & result }
%token A
%token B
%token BB
%token <int> C
%nterm <node::A> node_a
%nterm <node::B> node_b
%nterm <node::C> node_c
%nterm <int> start
%%
start: node_a { result = {.node = $node_a}; } |
node_b { result = {.node = $node_b}; }
node_c: C { $$ = {.val = $1}; }
node_a: A node_c { $$ = {.c = $node_c}; }
node_b: B node_c { $$ = {.c = $node_c}; } |
BB { $$ = {}; }
生成的解析器首先尝试默认构造节点。例如:
yylhs.value.emplace<node::A>();
然后才分配该值。例如:
yylhs.value.as<node::A>() = {.c = YY_MOVE(yystack_[1].value.as<node::C>())};
我需要的是跳过默认的构建 emplace
调用,并直接使用给定的子节点实际构建节点。
我在生成的头文件中找到了这个:
# if 201103L <= YY_CPLUSPLUS
/// Instantiate a \a T in here from \a t.
template <typename T, typename... U>
T& emplace (U&&... u) {
return *new (yyas_<T> ()) T (std::forward <U>(u)...);
}
# else
/// Instantiate an empty \a T in here.
template <typename T>
T& emplace () {
return *new (yyas_<T> ()) T ();
}
/// Instantiate a \a T in here from \a t.
template <typename T>
T& emplace (const T& t) {
return *new (yyas_<T> ()) T (t);
}
# endif
看来 emplace
方法实际上是为了能够直接构造最终值。
最佳答案
据我了解,本例中 bison 实现的目的是避免出现未初始化的 variant
的可能性。值,由于 Bison 的性质,这将导致未定义的行为 variant
。 Bison variant
不记录它初始化的对象的类型,因为这种情况在解析期间永远不会发生。因此,一旦解析器知道堆栈槽将包含哪种类型(也就是说,当它决定执行归约操作时),它就必须初始化堆栈槽,并且它只能使用默认构造函数,因为它没有尚未执行减少操作。
Bison 变体对象基本上是 union
,不是受歧视的 union ; variant
部分是指程序的控制流足以确定所包含对象的类型。所以不可能有未初始化的variant
这样的事情。 。而且,自从variant
从创建时刻到销毁时刻都有一个(隐式)类型,该类型在其生命周期内不能更改。
所以你想要的语义与这个设计不一致。这并不会让你的语义错误(或者,就此而言, Bison 的语义错误),但这确实意味着你不能使用 Bison variant
如果您想要“未初始化”变体的可能性。另一方面,我不确定如何实现这一点:
However, I'd like to keep some (actually most) of my AST nodes non-default constructible, because that would leave them in an invalid state. If I allow default construction of every node, I'd have to rely on runtime checks to ensure that every node I'm working with is valid.
问题是,AST 节点处于无效状态意味着什么?如果没有运行时检查,你怎么知道情况是这样呢?您是否希望它在使用时抛出异常或类似的异常?如果是这样,肯定需要进行运行时检查。
您可以使用 std::variant
来实现无效状态其第一个替代方案是默认可构造的 InvalidNode
类型或类似的类型,尽管当您尝试使用 variant
时,您必须注意它不会成为无操作而不是错误。目的。在这种情况下,您可以告诉 Bison 使用 std::variant
作为语义类型,尽管这不像使用 Bison variant
那样方便。 .
注意:在评论中(这根本不是澄清问题的最佳位置),您似乎在说您想要的变体始终是指针,这意味着“无效节点”类型本质上是 nullptr
。我确信这可以与 Bison 一起使用,但您需要以与使用 Bison variant
不同的方式来完成它。 s。 std::variant
多么般配啊我也不完全清楚:-)
关于c++ - Bison:避免语义值的默认构造,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66673413/