April 21, 2003 | ||
Metacode Progress | ||
On April 4th, I presented the Metacode extension at the ACCU Spring Conference in Oxford. The room was packed (which was very encouraging) and many attendees appeared excited about the ideas being presented (which was even more encouraging). A lot of ideas were raised at that presentation. Originally, I planned to bring up the Metacode extension at the October 2003 meeting of the C++ standardization committee. However, the ACCU presentation generated enough “buzz” to justify repeating the presentation for the standardization committee at the April meeting (which conventiently happened in Oxford April 6-11). That presentation too was well received. Here is a short summary of the extension... |
||
OverviewThere are four main components to the Metacode extension:
Metacode FunctionsA new keyword metacode introduces functions that are to be executed at compile time. template<typename T> metacode T power(T b, unsigned n) { T r = 1; for (int k = 0; k<n; ++k) r *= b; return r; } float a[power(2, 3)]; One of the more surprising rules for metacode function calls is that no implicit conversions are performed (by default) on their arguments. Another interesting feature of metacode functions is that any kind of function can be a metacode function. The Standard Metacode LibraryI envision a standard metacode library that implements a bunch of “magical” metacode functions and types. Code Injection PrimitivesI'm planning two code-injection primitives: One to inject declaration in a surrounding class or namespace scope, and one to inject an expression as a substitute for a metacode call. metacode double mypow(double b, int n) { using ::stdmeta::is_constant; if (is_constant(b) && is_constant(n) && n >= 0) { return power<>(b, (unsigned)n); } else { return-> ::std::pow(b, n); } } I'm hoping to get this example working in the relatively near future. The injection of declarations uses the metacode-> token sequence. metacode define_fields(array<type> types) { for (int k = 0; k<types.length(); ++k) { type FieldT = types[k]; id FieldName = id("field" + string_literal(k)); metacode-> { FieldT FieldName; // Metacode identifiers } // translated according } // to their types. } The types type, array<type>, and id are to be imported from stdmeta. Metacode BlocksMetacode blocks were actually the original idea that lead to all this. template<typename T> struct S { metacode { // Start metacode block if (stdmeta::typevar<T>().is_reference()) { stdmeta::error("No reference, please."); } } // ... }; I'm not quite sure yet how hard this will be to implement. |
||
Posted by Daveed at 09:24 AM
| Comments (15)
|
||
I'd love to see all of the trig, log, sqrt, pow, etc. functions work at compile time if you feed it floating point constants, among other things ("export metacode" is inevitable). We can work with floats/doubles at compile time this time around, right?
http://www.boost.org/libs/type_traits/ is the obvious stuff you should add to the type manipulation stuff. You should look at OpenC++ for some other stuff to add, the most important I can think of is a unique identifier generation function. Useful for local temporaries and local labels (maybe two versions, one that is deterministic and generates the same id each time the metacode block is used in different translation units, and one that generates completely random ones).
p.s. you didn't initialize the k in define_fields.
(To Pete; message of 5/5/2003; 7.59am)
Thanks for noting the missing initializer: It should be fixed now.
Yes, you can work with floating-point values at compile time. (In fact, I might resurrect floating-point nontype template arguments in our implementation.)
Type-manipulation-wise, I expect anything a compiler must be able to do will be available in some way (including things that are not currently possible in Boost).
I've looked at OpenC++, but the model that is chosen here is different: I don't expose an AST because there is no way to have a _simple_ AST model for C++ (too many language concepts and constraints). (I have another project called Xroma that does go that route: It is very nice, but Xroma is a much simpler language. Anyway, that project is on the back burner for now.) Still, the "unique identifier" utility might be useful (and is not hard to implement).
Make me think about Aspect programming.
Would be interesting to read about what
is in common and what is different from AspectC++.
Hmm, maybe I'm just stupid, but what problems specifically are you trying to solve with the metacode language extension? ie. What is the rationale for the extension?
Cheers,
Mark.
The rationale is that we are drifting more and more towards generating code at compile time to make efficient code or syntactical sugar now that compilers are up to par. The next C++ standard will dictate how C++ will look like for years so it is important that something like this gets in for sanity's sake. The current ways of generating stuff for C++ at compile time ("template metaprograms" and using a preprocessor (there are other ways also but they require external tools as opposed to using what the standard says is available on every compiler)) are hard and not powerful enough. They are hard because you have to go through a lot of loops (recursion and tricky syntax) and really know every single detail in the standard to do some of the advanced stuff (ignoring the fact that every compiler acts differently in places). Metacode just makes it a lot more friendly and adds some stuff you can't do currently.
It's sort of like how PHP (or javascript even) has powerful but simple constructs to generate HTML code. But metacode is better because it works at compile time (as opposed to preprocessing time) so it can generate code after analyzing the syntax tree (vs looking at random string tokens) and have enough information about the tree to do stuff (like do complicated function/type matching vs just looking at the indentifier) using already available C++ constructs and syntax.
To do what metacode is trying to do with an external tool, you would have to parse C++ code like a compiler. But by then, you've written a compiler front-end (and then some). There is no guarantee that other people will want to use your code if it requires an external tool, so having the standard say metacode is in is a good thing for everyone.
(To ken Walter; message of 5/5/2003; 9.43am)
I'm certainly no expert on AOP.
My understanding is that AOP attempts to achieve a new degree of “separation of concerns” by allowing a programmer to isolate code fragments that are to be executed at various locations. The AOP's system main job is mostly to weave the isolated fragments back into the proper places. AOP seems to imply the development of facilities to describe where the code fragments need to be inserted. Reading through the aspectc.org site, I find that part alone is (arguably) a bigger core language extension in AspectC++ than the metacode extension.
Metacode is lower level and more general. It might be possible to write a metacode-based library in support of AOP techniques for example. However, that may not be trivial since (at least for now) metacode does not allow for the transformation of closed scopes. I.e., you cannot add a class member to a class after it definition has been completed (i.e., the closing brace has been seen by the compiler).
The only commonality that springs to mind is that both extensions attempt to codify compile-time transformations.
Posted by: Daveed on May 7, 2003 04:01 PM(To Mark; message of 5/6/2003; 2.30pm)
Pete's reply (5/6/2003 7.30pm) is right on.
Metacode is not an answer to one particular problem to solve, although my pet problem has been the development of high-performance numerical libraries. Instead the ambition is to provide a low-level facility to solve or aide a variety of programming challenges.
Nonetheless, some of the specific problems that metacode might help with are:
• “Middleware” (distribution, persistence, ...)
• ABI bridging (e.g. calling Sun C++ libraries from code compiled with GNU C++)
• API transitions (transparently translating an old API into new ones)
• Component-specific optimizations (including everything template metaprogramming has achieved)
• All kinds of instrumentation
Try to make the font of this page relative in size. It is very hard to read (without a headache) on a high resolution display. I will try again to read it if it gets bigger. :-)
And thanx again for the book!
Posted by: Attila on May 8, 2003 02:41 AMCan you add some mechanisms to metacode to allow people to write useful "keywords" like the following (ideally the #defines would be replaced with something else so we can put them in namespaces):
#define ret_t <something here>
// assume my compiler is cool enough to allow this non-standard behavior
void (*foo())(unsigned=0)
{
ret_t ret(srand);
// generates:
// const bool <unique-id> = true; /* possibly, only here to stop typedefing ret_t */
// typedef void (*ret_t)(unsigned=0);
// ret_t ret(srand);
ret_t wow;
// generates:
// ret_t wow;
return ret;
}
// would be ideal if it could handle
foo wow()
{
typedef boo foo;
return ret_t(0);
// generates something like the following:
// typedef boo foo;
// typedef ::foo ret_t;
// return ret_t(0):
}
int main()
{
foo()();
return 0;
};
#ifndef NDEBUG
#define print_params() <something>
#else
#define print_params() ((void)0)
#endif
long foo(int a, const string &b, ...)
{
print_params();
// should generate something that acts like the following:
// dbg::indent <unique-id>; // this one is for adjusting the indentation level and resetting it back when the function returns
// dbg::out << "long foo(int a, const string &b, ...)\n[a = " << a << ", b = " << b << "]\n" << dbg::flush();
}
// this one might be problematic
#define me typeof(*this)
#define cme typeof(typeof(*this) const)
template<class T>
metacode base_(unsigned i)
{
<something>
}
#define base base_(0)
#define base0 base_(0)
#define base1 base_(1)
...
#define base10 base_(10)
struct foo : a0, a1, a2 {
me(int p0, const string &p1) : base(p0), base1(p1) {}
// generate something that acts like:
// foo(int p0, const string &p1) : a0(p0), a1(p1) {}
cme & bar() const { return (cme &) base::bar(); }
// generate something that acts like:
// const foo & bar() const { return (const foo &) a0::bar(); }
};
Good luck in October.
Very nice. Will it be possible to return-> code blocks? If so, you might be able to implement swap as follows:
template < class T > metacode
void swap(T &x, T &y)
{
if (is_accessable(x.swap(y)) {
return->x.swap(y);
} else {
return-> {
T temp(x); x = y; y = temp;
}
}
}
(To Pete; message of 5/17/2003; 9.44pm)
I'm not sure I entirely understand the functionality you're after. I suspect that the Metacode extension will allow you to do some of what you want, but it may take some new idioms (i.e., it might not be as "direct" as what one could ideally expect: a consequence of striving for a “primitive”) rather than a ready-made solution.
I'm interested in hearing more of your thoughts. Perhaps some details of how you see things working.
(To Joe; message of 5/19/2003; 8.40pm)
I currently do not anticipate allowing more than expressions in return-> statements (because the metacall was an expression context itself).
However, you can achieve what you want by preceding the return-> statement by a metacode-> statement that injects (if needed) an inline function (or template) with the needed functionality. In fact, for this particular case, you can just use a helper function.
(The first of your two return-> statements is fine. The call to is_accessible would need some tweaking, but the functionality you need is available.)
What immediately came to my mind when I read the code injection part was compile time introspection (maybe you intended something else?). Not only having (many) operations on the types/identifiers, but querying the compiler if you are currently inside a function/struct/union/class definition and being able to do limited introspection on that as you are definining it.
Also, is is_accessable() equivalent to microsoft's __if_exists()? I think it would be much better if you could pass a quoted expression and have the C++ compiler parse it (like an eval() in javascript/perl/others) because at least then you have a little bit more control over lookup than just a simple identifier.
Similarly you can add an is_ambiguous() function so you could do is_ambiguous("foo()") which would return true if calling foo() right there would result in a compiler error that said it didn't know which foo() to call.
Maybe some more metacode examples would help.
Posted by: Pete on May 20, 2003 06:52 AMThis is all very interesting, and I very much hope that something of this nature gets into the language. Just as the first incarnation of c++ gave us objects, the last standardisation gave us the orthogonal static type associations of templates (and all the static metaprogramming and generics that openned up), I see this next standardisation as confronted with the new zeitgeist of our times, generative programming, which it must embrace or fall by the side of technological advancement.
But there are goals that whatever framework eventually gets looked at must accomplish, or it is just "template metaprogramming made easy". The most important of these in my opinion is: "does the framework minimise the need for pattern duplication in program construction?"
OOP tackles the minimisation with objects that can be snapped together through inheritance or containment. AOP tackles the same problem with aspects and rules for weaving the aspects together into concepts, which helps solve things like synchronisation and error checking code duplication more intimately tied within methods.
But there are a few things where we still need to duplicate code, or fall to preprocessor metaprogramming (or outside c++ entirely).
One of the big ones is serialisation, which presents two problems. First, there is the production of a unique type identifier to carry the information out of the type system for whatever factory will pop it back in later on. One of the common, simple solutions is to use the stringised classname, possibly scoped by namespaces it lies within. But that means either coding an id method and directly putting the string in by hand or falling to the preprocessor's stringiser (or a suitably flexible wrapper like BOOST_PP_STRINGIZE). And you also have the entire problem of automating the recursive serialisation of members down to POD or other primitives and doing the same up the inheritance graph. This can be done either by hand (which how alot of serialisation frameworks work, unfortunately), or you can go the preprocessor route again. However, the only flexible preprocessor solution I have seen involved the declaration of member lists and using a whole new format for the declaration of a class which looked something like
#define CLASS_NAME FrameworkTestSimpleClass
#define PRIVATE_STATE_OBJECTS ((int, (i, BOOST_PP_NIL)), ((wchar_t, (j, BOOST_PP_NIL)), \
BOOST_PP_NIL))
#define DEFAULT_STATE_DECLARE
#define FULL_DEFAULT_CTOR_DTOR_DEFINE
#define MAKE_SERIALISABLE
#include "objectbuilder.h"
public:
bool myFunct();
private:
virtual bool myOtherFunct();
#include "objectbuilder.h"
which is quite foreign to c++ style. It had the advantage that one could build other useful things like member-wise ctors and such with the information in the list, but you're now in preprocessor land.
And I think that serialisation is a really good test for a metaprogramming framework. In particular, the capabilities required for serialisation to be automatable are unique identifier generation that is consistent across compiles (possibly stringisation of fully scoped type names), enumeration of members, and walking the inheritance graph.
Now, I've read through many of the postings on the newgroups on this particular metacode implementation as well as the dkuug slides but still can't quite figure out if these capabilities are in particular covered. The baseN example above shows basic association, but it is unclear if enumeration is possible (and of members). I see the ability to lookup names as strings, but wonder if the ability to go the other way is available.
The reason I ask about this is because if (I hope when) this stops being an experiment and starts being a proposal, there should real solid examples available of how this mechanism really does save code duplication and helps thrust c++ to the forefront of the generative programming wave industrialising our profession.
Some other capabilities I am curious about are:
a) enumeration of automatics in scope (likely very helpful in implementing a scalable lambda facility with both static and runtime binding capability / optimisations)
ii) along the lines of Pete's questions, will metacode allow "keyword" injections which can even carry around domain specific optimisations (like the swap example or the classic EXP(A, B) example from Czarnecki and Eisenecker). If so, then I think we're really looking at solid foundations for intentional programming, at least precompile.
three) what is the current status of file accessing? In particular, I want to be able to control my translation unit's complete design without any recourse to the preprocessor. I want to be able to scan a file, include only what I desire, perhaps have configuration scripts I can respond to, etc. I want c++ to be my solution, not c++ + XYZ scripting + ABC otherCodeGenerator + whoKnowsWhat.
and 4) do you see any other, particular benefits to pattern generation such a facility might bring (a la Alexandrescu)?
Basically, I think it is important to establish some good metrics as soon as possible on the removal of code duplication and the automation of code composition so that one can walk into the standardisation next round ready. I'd hate for such a facility to be skipped over by the c++ community at this stage, because I think our industry is just beginning to reap the benefits of adaptable metaprogramming and could find other options more and more inviting if c++ fails to offer the flexibility desired.
Posted by: galathaea on December 7, 2003 09:19 AMMetacode would be very useful to produce bindings for other languages, like Boost::Python. It means all classes, methods, etc. would be navigable. Very similar to what Python can do at run-time.
C++ is becoming a last-resort language, used when performance is a must like in video games, system programming, etc. Nothing is more efficient at run-time than do the job at compile-time. I really hope to see something like metacode in C++ some day...
Posted by: Nicolas Fleury on February 5, 2004 09:44 PM