1

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;
    }

};

Tom
  • 1,235
  • 9
  • 22
  • 2
    I suggest that you look at the generated sources, whatever is going on will likely be fully apparent there. One thing to note, I do not believe that I have ever used a redefined YYSTYPE and %union at the same time. – SoronelHaetir May 08 '20 at 17:48

2 Answers2

5

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];
                              ^
Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • Thanks for the great answer once again! Yes, what you said makes sense, I was partially following some very old examples I found. – Tom May 08 '20 at 19:18
  • So to summarise, I cannot use %union with shared_ptr because it is not a traditional semantic type, and also I cannot used %define with shared_ptr because it has no members so would not work with the derived Node classes. There is the option of using downcasting as well. What do you think is the best method to manage memory in this circumstance? – Tom May 08 '20 at 20:35
  • 1
    @Tom: it's not that it is not a traditional semantic type. It's that C++ doesn't allow `shared_ptr` to be a member of a `union`. And also that C++ doesn't allow the default construction of a `union` with members which are not trivially constructable. This really has little to do with bison. You could use `%define` with `shared_ptr`, but then you could only use one type of shared pointer (no union to allow you to use multiple ones), and things will fall apart if the stack is reallocated. – rici May 08 '20 at 20:40
  • 1
    So basically, you can only use the C API if you use what used to be called "Plain Old Data" (POD) datatypes; that is, datatypes which would feel right at home in C. If you want to unleash the C++ superpowers, you need to use the C++ API. See the bison manual for details. (There are lots of details.) It's a bit of a learning curve, but if you feel more at home in C++, it's obviously worthwhile using a parser generator which can generate real C++, not just C disguised as C++. Bison is such a generator, but you need to ask for what you want. – rici May 08 '20 at 20:42
  • You asked for my personal opinion, which is something you have to be careful about in StackOverflow, where opinions are supposedly not allowed. But, as it happens, I have one. I would use an ordinary C-style pointer, because you're building a tree and a shared_pointer would be massive overkill. As long as the AST is a tree, tracking object "ownership" is trivial. If you later want to do common-sub-expression elimination or some such, then you will need some other memory-management regime. Its possible that will involve shared pointers. But me, I'd leave that for later, in case later is never. – rici May 08 '20 at 20:45
  • That makes a lot of sense. To be honest, I only really wanted to use smart pointers so that it would automatically free up memory. Thinking about it now, it does seem like a bit of a overkill. Thanks for all the advice, I'm looking into C++ bison now. I got some thinking to do. Till next time :) – Tom May 08 '20 at 21:03
0

this is a limitation of union in C++, a member of a union cannot have a constructor.

union {
    std::shared_ptr<BlockNode> blockNode; //not allowed
    std::shared_ptr<Node> *testNode;      //allowed
}

so,in your case, it is not necessary to use shared_ptr, just

union {
    Node *testNode;      
}
blank
  • 1