Herb Sutter: "Metaclasses: Thoughts on generative C++"

Thought people might be interested.

Blog post: https://herbsutter.com/2017/07/26/metaclasses-thoughts-on-generative-c/

My 10,000 ft view: Seems quite powerful. Feels a lot like procedural macros, but crucially, with access to type and other semantic information for existing declarations (which can then be used to generate new declarations); also more lightweight. This is pretty much my #1 pet language feature I’ve always wanted to see in Rust in some form… but that’s just me.

Two example snippets pulled from the paper:

Basic code injection

constexpr {                          // execute this at compile time
    for... (auto m : $T.variables()) // examine each member variable m in T
        if (m.name() == “xyzzy”)     // if there is one with name “xyzzy”         
            -> { int plugh; }        // then inject also an int named “plugh”
} 

Writing as-if a new ‘language’ feature using compile-time code

$class interface { // see §3.1
    constexpr {
        compiler.require($interface.variables().empty(),
                         "interfaces may not contain data");
        for... (auto f : $interface.functions()) {

            compiler.require(!f.is_copy() && !f.is_move(), "interfaces may not copy or move; consider a" " virtual clone() instead");

            if (!f.has_access()) f.make_public();
            compiler.require(f.is_public(),
                             "interface functions must be public");
            f.make_pure_virtual();
        }
    }
    virtual ~interface() noexcept { } 
};

// User code (proposed C++)
interface Shape {
    int area() const;
    void scale_by(double factor);
};
8 Likes

I’m sure there are use cases for this sort of patching but it has to lead to messy code, right?

Consider the plugh case. Users of the patched type will encounter interesting error messages (although not uncommon in C++) when trying to access the plugh member that couldn’t be injected because of conditions not being met when the constexpr was evaluated.

Then again, I’m not an avid user of macros so…

1 Like

Btw, having multiple constexprs modifying the same item is a lot like having multiple mutable borrows of the item, and they’re all potentially messing with each other – just taken to a whole new level (the AST).

You’d want to require ownership :smile: or have each constexpr return a new type.

I don’t know how it’s implemented, but I wouldn’t think any sort of cyclic mutation by multiple constexprs (i.e. one introducing a member and then another deleting it, or one setting one visibility and another changing it) would be possible. The examples are additive, with metaclasses injecting members, visibility, requirements, etc… without ever removing or overwriting anything. It seems entirely possible to me to implement this as an append-only system to ensure this. The use of metaclasses as constraints seems to suggest that this is the case, since otherwise any guarantee provided by the metaclass could be undone for any particular instance and the constraint would be useless. This also suggests that a non-linear execution model would have to be used, since requirements injected by a metaclass would have to apply to members injected/modified by ancestor, sibling, or descendent metaclasses.

is there anything in doing something like this for the purpose of making it easier to specify rust in a similar manner that mr Sutter thinks it will help C++ save time in lawyering?

I believe Rust macros (especially procedural macros) could cover most of the same use cases as these metaclasses.

Rust macros including procedural macros are text processors. How would you write a macro that iterates over all methods of a struct? Or over all traits implemented for a struct? You cannot ask how many methods a type has, or if two types are equal. E.g., how do you detect whether a type is an i64 or an u64 in a procedural macro?

Metaclasses have type information and can do all these things. What we need is something that can do AST->AST during type-checking, macros aren't it.

3 Likes

The author gave a fantastic talk on this design last week at CppCon. A lot of the ideas and philosophy are relevant to proc macros in Rust.


8 Likes

I would like to know if there is any "plan" about this. For proc macros to be able to do any of this they would need to have access to type information. I don't even know if the plan is to pursue this as an extension to proc macros, or as a completely different language feature.

As far as I know proc macros do not and will not get any type information, at last not beyond what can be inferred directly from the syntax.

As proc macros can introduce new code and with it both new types and new implementations for existing types the information it got can indirectly change in non obvious ways (through wild card implementations) after it was executed. Leading to possible different results depending on the order of which proc macros where resolved.


(I’m not 100% sure the following section as it touches many tricky corner cases in practice)


On the other hand rust can go a similar way as C++ currently does:

  1. add reflection features

  2. add (more/extensive) compiler time evaluation

    • including const block which are guaranteed to be evaluated at compiler time
    • optimally through optionally: also including some compiler time evaluation wrt. to function using alloc, e.g. a HashMap which is created and destroid in the same function could be used in compiler time evaluation as no alloc memory is “leaked” from compiler time to runtime, even if there are some ways to handle that too
  3. make sure reflection functionality can be evaluated at as much as possible at compiler time

  4. allow types to vary depending on compiler time evaluated control flow which can be combined with impl Trait

  5. possible more to support more thinks, through 1-4 already enables a lot if I have not overlooked something

This with proc macros and impl Trait would already allow many use cases you might want to have.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.