Search code examples
c++bisonsmart-pointersflex-lexer

std::shared_ptr in Bison causing member error


I'm trying to make bison more memory efficient by using std::shared_ptr. I do not want to use raw pointers. I'm using a node system as the parse tree so I define YYTYPE as std::shared_ptr<Node>. After running it with some simple grammar, I get the compile error:

C2039 'blockNode': is not a member of 'std::shared_ptr'

I find this strange as the equivalent code ran in C++ works just fine

std::shared_ptr<Node> test = std::make_shared<BlockNode>();

What am I missing?

Requires

%code requires {
    typedef void* yyscan_t;
    #include "node.h"
    #include <memory>
    #define YYSTYPE std::shared_ptr<Node>
}

Union

%union {
    std::shared_ptr<BlockNode> blockNode;
    std::shared_ptr<TestNode> testNode;
}

%type <blockNode> input
%type <testNode> expr

Grammar

%start program

%%

program : input { *result = $1; } // <- error here
        ;

input: '\n'      { $$ = std::make_shared<BlockNode>();} // <- error here
     ;

%%

Node.h

class Node /*: public std::enable_shared_from_this<Node>*/ {
public:
    std::string myString;
    Node() {}
    ~Node() { std::cout << "destoryed" << myString << std::endl; }
};

class BlockNode : public  Node {
public:
    BlockNode() {
        myString = "block node";
        std::cout << "created" << myString << std::endl;
    }

};


Solution

  • The first thing you should know is that this design cannot work. If you use the default C API for bison, you cannot use a semantic type which is not trivially copyable, because bison will bytewise copy its stack if it needs to reallocate it (and I believe there are other issues having to do with overwriting bytes without calling destructors). If you want to use shared pointers, you should work out how to use the C++ API, which I think is reaching some kind of maturity (although I haven't used it much). You'll probably be much happier with the result.

    Regardless of that, there are some other issues with your code.

    First, modern bison applications should not #define YYSTYPE, not even inside a %code requires block. You should instead use

     %define api.value.type { /* SemanticType */ }
    

    If you had done that, bison would have been able to tell you that you cannot use both a %union declaration and a fixed api.value.type. If the semantic type is a union, it's a union. It cannot also be a shared_pointer. Since you seem to want it to be a union whose members are both shared pointers, then it's a union and you don't want to define it otherwise.

    If you do use #define YYSTYPE, and also use %union, then you'll find that the %union never gets applied. %union inserts a default definition of YYSTYPE (as union YYSTYPE), but your explicit definition of YYSTYPE overrides that. But bison doesn't know you've done that -- it doesn't become apparent until the C compiler actually compiles the generated code -- so it rewrites semantic value references using the tags you provided in the %type declarations. In other words, when you say %type <blockNode> input, bison will automatically change any reference to $n where that refers to an instance of the input non-terminal by adding a field reference, as though you had written $n.blockNode (which, of course, you must not do because bison has already added the field reference). But the #define-overridden YYSTYPE is not a union, it's a shared_pointer<Node>, and shared_pointer<Node> does not have a blockNode member, as the C++ compiler error message indicates.

    Similarly, in the rules for input, the %type declaration causes bison to emit code which will assign to the (non-existent) blockNode member.

    By way of illustrating my first point -- that you cannot use shared_pointer as a semantic type or union member with the C code generator -- I "fixed" your code by applying the suggestion above (that is, remove the #define YYSTYPE, and made a more or less minimal set of changes to avoid other bison and compiler errors, resulting in the following reduced reproducible example:

    File tom.yy

    %code requires {
        #include "tom_node.h"
        #include <memory>
    }
    
    %code {
        std::shared_ptr<Node> result;
        void yyerror(const char* msg) {
          std::cerr << msg << '\n';
        }
        int yylex();
    }
    
    %union {
        std::shared_ptr<BlockNode> blockNode;
        std::shared_ptr<Node> testNode;
    }
    
    %type <blockNode> input
    
    %%
    
    program : input  { *result = *$1; /* ?? I don't know the actual intent */ }
    
    input: '\n'      { $$ = std::make_shared<BlockNode>();}
    

    File tom_node.h

    #ifndef TOM_NODE_H
    #define TOM_NODE_H
    
    #include <iostream>
    #include <string>
    
    class Node /*: public std::enable_shared_from_this<Node>*/ {
    public:
        std::string myString;
        Node() {}
        ~Node() { std::cout << "destroyed" << myString << std::endl; }
    };
    
    class BlockNode : public  Node {
    public:
        BlockNode() {
            myString = "block node";
            std::cout << "created" << myString << std::endl;
        }
    
    };
    #endif
    

    The result is a sequence of similar errors, all dealing with the fact that std::shared_pointer is not a trivial type. Here's the first few:

    $ bison -o tom.cc tom.yy
    $ gcc -Wall -o tom tom.cc -ly
    tom.cc:956:9: error: use of deleted function ‘YYSTYPE::YYSTYPE()’
     YYSTYPE yylval;
             ^~~~~~
    tom.cc:104:7: note: ‘YYSTYPE::YYSTYPE()’ is implicitly deleted because the default definition would be ill-formed:
     union YYSTYPE
           ^~~~~~~
    tom.yy:15:32: error: union member ‘YYSTYPE::blockNode’ with non-trivial ‘constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = BlockNode]’
         std::shared_ptr<BlockNode> blockNode;
                                    ^~~~~~~~~
    tom.yy:16:27: error: union member ‘YYSTYPE::testNode’ with non-trivial ‘constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = Node]’
         std::shared_ptr<Node> testNode;
                               ^~~~~~~~
    tom.cc: In function ‘int yyparse()’:
    tom.cc:985:30: error: use of deleted function ‘YYSTYPE::YYSTYPE()’
         YYSTYPE yyvsa[YYINITDEPTH];
                                  ^