class Expression {
enum Tag { litteral, symbol, funcall, conditional,
while_loop, sequence } _tag;
union {
Value *litteral;
std::string *symbol; // or a true symbol?
std::vector<Expression> *composite;
} _val;
};
And you could probably use templates or macros to autogenerate most of those constructors/destructors automatically.I swear I am not. This is will be production code, for which I earn my salary. Your code is terser, but still fundamentally the same as mine. It's still unsafe (we could access the wrong union member), and it still has to work around incomplete definitions and the lack of trivial constructors (I wouldn't use pointers otherwise).
Automating my constructors and destructors would be amazing. Unfortunately, I don't know how to do it —not without writing over a hundred lines of cryptic boilerplate first.
"...could probably use templates or macros to autogenerate.."
... which kind of highlights that ML example even more. In Ocaml or F# that original definition would have been more or less enough to start writing nice, efficient parsing code.
The sum types are not the only thing, IMO, that ML-family has going for it. As for example F# contains pairs and lists as syntax level constructs (rather than library add-ons) lot of code can be written at a really high level before optimization - in the same language - if it's needed.
If you stick to your code you should at least:
* add a copy constructor or delete it explicitly
* pass in the vector as unique_ptr to the constructor. This way the "takes ownership" will be clear without the need of a comment.
* add your missing includes e.g. vector
* stop using _ as variable prefix, it's forbidden
* throw from the constructor, don't exit
Boost variant + boost vector will allow you to eliminate all the manual memory management (boost vector allows incomplete types).
* Do you have a link about the _ (underscore) being forbidden? Our current coding rule make us use them right now.
* Is there a standard exception object I could throw? If I could just write something like `throw std::invalid_argument("what String");` that would be terrific. I don't want to construct a whole class just for that. (Maybe I have to, though?)
I'll also seriously consider boost. While it's a huge dependency, we are on Debian GNU/Linux, so it's probably not that bad.
* double underscore or underscore + capital letter is reserved in any scope
* underscore is reserved in global scope
Your code is allowed in fact, sorry. My personal rule is that I never start an identifier with an underscore anywhere.
The code will certainly be more verbose than ML, but still better than roll-your-own.
Egg.Variant is an high quality one: https://github.com/eggs-cpp/variant .
We are likely going to get a std variant type in the next standard, either as a library component or as a language feature.
That's a bit too restrictive. Rust, Swift or Kotlin are not MLs.
If I didn't use pointers, I would have 2 errors. One would be about the use of an incomplete data type (Expression), and the other about trying to put something that doesn't have a "trivial constructor" in a union (String, Value, and std::vector<Expression>).
I can't use references either, because I would have to initialise them in the initialisation list of my constructor, which I can't do until I know the tag. I could still initialise the tag first, but that's specified by the order in which the data members are defined, rather than the order of the initialisation list —a rather dangerous idiom to rely on, should anyone modify the code.
You are right about the issue with std::vector and incomplete types. It will be possibly resolved in the next standard. In the meantime you can use something like boost::container::vector which explicitly support incomplete types.
The highest level abstractions, are you kidding me?
I am currently writing a little interpreter in C++. I would have used Ocaml, but the rest of the project is in C++. Well, just try to write an abstract syntax tree in C++. In languages such as ML and Haskell, this is easy:
And I'm pretty much done. Now in C++: Don't forget the cpp file either: I may use smart pointers instead of doing my own RAII by hand, but that's still a hassle. Some may point out that my ugly unsafe union type is not the way to to this, I should use a class hierarchy and polymorphism. But just imagine the sheer amount of code I'd have to write.C++ is a low level language. It's high level pretense is nothing but a thin veneer that cracks under the slightest scratch. Or, someone show me how to implement an abstract syntax tree in less than 20 lines of code. I'm lenient: Haskell and ML only need 6.
(Yes, I'm using Sum types, a feature that's pretty much unique to ML languages. But no, that's not cheating: providing enumeration and unions but somehow failing to combine them into sum types is a serious oversight.)