Retrieving Base class from antlrcpp::Any
Asked Answered
E

2

6

I'm currently porting over some code from the C# version of ANTLR4 to the C++ target and I'm currently running into some issues. The way I built my AST in C# was to create a base class (let's call it Base) and derived classes (let's called it Derived) with virtual functions which I can use to implement said classes.

However trying to convert this code to C++, I keep getting bad_cast exceptions I've narrowed it down to antlrcpp::Any not correctly casting a derived class into its base class. For example:

class Base {
public:
    virtual std::string ToString() const { return "Base\n"; }
};

class Derived : public Base {
public:
    std::string ToString() const override { return "Derived\n"; }
};

int main() {
    Derived value;
    std::cout << value.ToString(); //Obviously prints out Derived
    Base& base_value = value;
    std::cout << base_value.ToString(); //Still works exactly as expected and prints Derived

    auto any = antlrcpp::Any(value);
    auto derived = any.as<Base>(); //internally uses a dynamic_cast and throws a bad_cast 
    std::cout << derived.ToString(); //never gets to here
}

I initially thought it was maybe because it only worked with pointers, however

auto any = antlrcpp::Any(new Derived());
std::cout << any.as<Base*>()->ToString(); //throws bad_cast

I changed the dynamic_cast into a static_cast inside the header and it will cast, however it prints out "Base". And a C-style casting outright causes a crash when any data members get accessed.

How exactly do I use antlrcpp::Any to get a Base class? Is there anything obvious I'm missing?

And if this isn't possible, how exactly do I work around this? There's the .is() method, however there's many cases where checking to see the return value of visitor is of a certain type is just not feasible (such as with expressions, there can be 30-40 operators).

Erdmann answered 13/12, 2017 at 8:25 Comment(3)
Possible duplicate: c++ heterogeneous container, get entry as type. If you need to store objects derived from Base, you can always store Base* and dynamic cast it to the needed type on retrieval.Raffia
It isn't necessarily a duplicate as we're asking separate questions. However that solution does seem to be applicable here. antlrcpp:Any((Base*)value) and any.as<Base*>() does correctly return a Derived class. It's a little disappointing now because antlrcpp::Any doesn't support unique_ptr and now I have to manage memory. The other way they'd be copied.Erdmann
@Erdmann can you explain in detail how? I tried to do that but it doesn't work for me, I am loosing type information.Deguzman
B
1

The antlrcpp::any class isn't made for that kind of scenario. It's not a universal Variant implementation.

If you need that you should think about an own Variant implementation using a union for the various types etc. which doesn't work with type erasure.

Unique pointers are probably not a good idea in this context in general, as they don't support the copy semantics (and you would need that here). For evaluation with visitors better go with a shared_ptr instead.

Bridegroom answered 14/12, 2017 at 8:45 Comment(1)
Combining the comment and this answer works just fine. Using a shared_ptr<Base> and casting all my derivatives as Base solves the memory issue of using raw pointers and works perfectly. I don't see why it'd be necessary to implement my own variant class.Erdmann
E
1

This is how I made it work:

A bit of background - I am creating a whizzbang optimised "Function Applier" a class that applies its complex function to records of data passed in. I didn't want to visit the parse tree every time I got a new record of data, so I created my optimised whizzbang tree like object to apply the function to the data.

Now the base class of this object and all derived classes will have an .apply(some_data) function so they all derive from the base type, but all perform different functions when .apply() is called, which includes calling .apply() on child objects belonging to them.

The most basic function-applying object I am creating returns a constant data structure with a double representing the constant integer in it when passed a record of data so:

 applier_instance.apply(some_data)

returns:

 {23.0, ...}

no matter what data is passed to it. Pass it {"CAT","DOG","FOO"} it will return {23.0, ...}

I want this applier_instance object to be created when the antlr parser sees the string "23".

Now if you've got this far, you'll realise I need to pass this object up through multiple visitors, and much of the time this will be done via an antlrcpp::Any object and multiple calls to very similar default visitor methods such as the one in the following antlr generated visitor code, which visits an 'Expression' node of the parse tree:

virtual antlrcpp::Any visitP_expression(MyParser::P_expressionContext *ctx) override {
  return visitChildren(ctx);
}

..and finally out of my 'start' rule...

virtual antlrcpp::Any visitStart(MyParser::StartContext *ctx) override {
  return visitChildren(ctx);
}

Just like you I ran into an issue trying to move unique pointers up through the default functions.

My answer was to do this in my visitor function for handling integer literals on the parse tree:

antlrcpp::Any visitP_NUMBER(MyParser::P_NUMBERContext *ctx) {
    IFunctionApplier* fa = new IntegerLiteralApplier(stoi(ctx->getText()));
    return fa;
}

I create my raw pointer to the IFunctionApplier interface (which has the virtual .apply(some_data) method defined) and created a new IntegerLiteralApplier object which inherits from that interface.

Where is the delete for the new in the previous block of code I hear you ask?

There isn't one - I turn my raw pointer into a unique one, as soon as it pops out of the top of the antlr generated function calls:

...
AttributeMapParser                      parser(&tokens);
//Get 'start' rule of the parser to make a parse tree (Abstract Syntax Tree)
AttributeMapParser::StartContext*       tree =      parser.start();


////Walk and visit the tree returning a raw pointer to a function applier
auto fa_raw = visitor.visitStart(tree).as<IFunctionApplier*>();

//convert pointer fa_raw to a unique pointer 
auto fa = std::unique_ptr<IFunctionApplier>(fa_raw);

//Clear up the parser and tree
parser.reset();

//start using the unique pointer
auto result = fa->apply(some_input_data);

Now I warn you, I'm a beginner at c++, porting over Python code which duck-typed the .apply(some_data) method, so read my answer with some caution, but

  1. It works
  2. A guy with 25 years experience of c++ looked over my shoulder afterwards and said it looked, and I quote, "OK"
  3. There are few resources for antlr c++ runtime on the web, so I thought I'd share my experience

Good luck!

p.s. Edit 2019/11/13

To show how, when going up the tree I then use the pointers created at the bottom of the tree:

//For 'not' atom
        virtual antlrcpp::Any visitAtom_not(MyParser::Atom_notContext *ctx) override {

            auto raw_exp = visit(ctx->atm).as<IFunctionApplier*>();
            auto unique_exp = std::unique_ptr<IFunctionApplier>(raw_exp);

            IFunctionApplier* fa = new NotApplier(std::move(unique_exp));

            return fa;          
        }

See how I am casting the antlrcpp::Any to a raw pointer to the interface IFunctionApplier, then turning that into a unique pointer and moving it into my parent object (so here a literal integer 'functionapplier' gets passed in to the 'not' 'applier', and has become a unique pointer.

The raw pointer fa is returned from the visit, and will be passed up to the next ctx call.

Erde answered 5/11, 2019 at 10:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.