c++ - Bison:避免语义值的默认构造

标签 c++ bison

我使用 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/

相关文章:

c++ - 即使函数存在,也出现 undefined reference 错误

bison - 如何向 yylex 添加其他参数(在 bison/flex 中)?

c++ - 解析时即时收集类定义

c++ - 通过SHELLEXECUTEINFO将参数发送到.exe

c++ - 如何从模板化基础中消除多个继承的 typedef 的歧义?

c++ - 你如何在 visual studio 2013 中更改你的应用程序图标?

c++ - 使用 C++ 检查一个特定进程是否在 Windows 上运行

c++ - 有弹性替代品吗

C++ 作用域?将数组传递给函数时出现问题

c - flex bison 和 yyparse 的段错误