Using the ANTLR4's C++ Target's "Any" Class with a Purely Virtual Base Class

463 views
Skip to first unread message

jjsz...@gmail.com

unread,
Jun 20, 2017, 4:26:52 PM6/20/17
to antlr-di...@googlegroups.com
It seems like the ANTLR4 C++ target's Any class has issues handling polymorphism with pointers. I am not quite sure how to put this into words, but I can provide an example of what I am observing. I would like to be able to pass around pointers of a Base class and call its member functions, as shown below:

          #include "antlr4-runtime.h"

          #include <string>
          #include <iostream>

          class Base {
            public:
              virtual std::string to_string() = 0;
          };

          class Derived : public Base {
            public:
              std::string to_string() {
                return "foobar";
              }
          };

          antlrcpp::Any test() {
            return new Derived();
          }

          int main (void) {
            auto value = test();

            std::cout << value.as<Base*>()->to_string() << std::endl;

            return 0;
          }

However, the above code throws an exception `std::bad_cast` exception from within the `Any` class. I am not sure if this is by design or if there are some C++ issues preventing this from working... but can you advise on how to get this working? Any help here would be appreciated.

I can workaround this by explicitly stating `return (Base*) (new Derived());` in `test` but since forgetting this could blow up my code, I would much rather have the library take care of this for me.

As an aside, it would be very convenient if ANTLR4 had a way to specify the type of the abstract syntax tree to be something other than Any, because I would much rather use a pointer to my Base class for its type. Something like the Java interface, where we could specify ExampleBaseVisitor<Base*>.

Mike Lischke

unread,
Jun 21, 2017, 4:19:09 AM6/21/17
to antlr-di...@googlegroups.com

> #include "antlr4-runtime.h"
>
> #include <string>
> #include <iostream>
>
> class Base {
> public:
> virtual std::string to_string() = 0;
> };
>
> class Derived : public Base {
> public:
> std::string to_string() {
> return "foobar";
> }
> };
>
> antlrcpp::Any test() {
> return new Derived();
> }
>
> int main (void) {
> auto value = test();
>
> std::cout << value.as<Base*>()->to_string() << std::endl;
>
> return 0;
> }
>
> However, the above code throws an exception `std::bad_cast` exception from within the `Any` class.

That's surprising. I haven't seen such a problem in my tests. Maybe it's an object slicing problem? That needs to be investigated. However, the Any class is so simple (and header only) it should be easy for you to see what's going on.

> As an aside, it would be very convenient if ANTLR4 had a way to specify the type of the abstract syntax tree to be something other than Any, because I would much rather use a pointer to my Base class for its type. Something like the Java interface, where we could specify ExampleBaseVisitor<Base*>.

Two things here:

1) ANTLR4 generated parsers don't create an AST, but a parse tree.
2) The parse tree does not use the Any class. This class is used only in one place: in the visitor, to allow creating results of any kind.

In Java the visitor class uses virtual functions with generics to implement that flexibility. This is not possible in C++, as you cannot have virtual (runtime evaluation) template (compile time evaluation) methods. Hence I settled for a variant-like result type instead of a template parameter.

Mike
--
www.soft-gems.net

Justin Szaday

unread,
Jun 21, 2017, 4:14:43 PM6/21/17
to antlr-discussion
> Maybe it's an object slicing problem? That needs to be investigated. However, the Any class is so simple (and header only) it should be easy for you to see what's going on.

Perhaps? I have my theories that it is related to the fact the pointer gets put into a templated wrapper (called Derived). Since the wrapped value is not polymorphic, the  "dynamic_cast<Derived<T>*>(_ptr)" fails. Say if we added the following to my previous example:

        template <typename T>
        class Wrapper {
        private:
          T _val;
        public:
          Wrapper(T val) : _val(val) {}

          T get_value() {
            return _val;
          }
        };

        int main (void) {
          Derived* value = new Derived();
          Wrapper<Derived*> wrapped(value);

          Wrapper<Base*> *test = dynamic_cast<Wrapper<Base*>*>(&wrapped);
          ...

GCC will fail to compile this, reporting "error: 'Wrapper<Derived *>' is not polymorphic" (the Any class compiles probably successfully because it does not explicitly name types). This issue is probably what is causing the cast to fail. Short of rewriting the Any class, I do not know of a good fix for this. 

Two things here:

1) ANTLR4 generated parsers don't create an AST, but a parse tree.
2) The parse tree does not use the Any class. This class is used only in one place: in the visitor, to allow creating results of any kind.

In Java the visitor class uses virtual functions with generics to implement that flexibility. This is not possible in C++, as you cannot have virtual (runtime evaluation) template (compile time evaluation) methods. Hence I settled for a variant-like result type instead of a template parameter.

Ah, right. Pardon my lapses in verbiage, that is what I meant. OK, Any is proving to be useful anyway since some of my rules return types vs. expressions... In other words, never mind, I am sold on Any, haha.

Mike Lischke

unread,
Jun 22, 2017, 3:12:23 AM6/22/17
to antlr-di...@googlegroups.com

Ah, right. Pardon my lapses in verbiage, that is what I meant. OK, Any is proving to be useful anyway since some of my rules return types vs. expressions... In other words, never mind, I am sold on Any, haha.

Hehe, ok, good to hear. Maybe try wrapping your polymorphic classes in a non-polymorphic struct.


Reply all
Reply to author
Forward
0 new messages