📜 ⬆️ ⬇️

Metaclasses in C ++

In this article we will talk about the new proposed extension of the C ++ language - metaclasses. The coat of arms of Sutter and his colleagues worked on this proposal for about 2 years and, finally, this summer presented it to the public.

So, what is the "metaclass" in terms of the Sutter's coat of arms? Let us recall our C ++, the most beautiful programming language in the world, in which, however, for decades there have been approximately the same entities: variables, functions, classes. Adding something fundamentally new (like enum classes) takes a very long time and you can’t expect to wait for the inclusion of something you need here and now in the standard. But something really is not enough. For example, we still have no (yes, probably will not) interfaces as such (we have to emulate them with abstract classes with purely virtual methods). There are no properties in their full understanding, there are not even value-types (something that could be defined as a set of variables of simple types and immediately used in containers / sortings / dictionaries there without defining for them different operations of comparison, copying and hashing). And in general, something is constantly missing. Qt developers now lack metadata and code generation, which forces them to use moc. C ++ / CLI and C ++ / CX developers lacked ways to interact with the garbage collector and their type systems. Well, etc.

And let's imagine for a moment that we ourselves can introduce new entities into the language. Well, or if not directly "entities", but the rules for checking and modifying classes.

How it all will work. The emblem suggests introducing the concept of “metaclass” as a set of rules and code that will be executed at the compilation stage and on the basis of which the compiler will check the classes in the code and / or create new classes based on the above-mentioned rules.
')
For example, we want to have a classic interface in the language. What is an "interface"? For example, the C # language standard answers this question on 18 pages. And with this there are a number of problems:

  1. Nobody reads them.
  2. The compiler is absolutely not guaranteed to implement exactly what is written in those 18 pages of text.
  3. We are not able to verify the compliance of the compiler and the text in English
  4. For C ++, you would have to write the same specification and its implementation in compilers (and knowing C ++ is also much more complicated). And then see paragraphs 1, 2 and 3.

But let's say in simple terms that an “interface” is a named set of public pure-virtual methods to which, at the same time, no private methods or data members are attached. Everything! Yes, maybe I have now missed some small detail from those 18 pages of specification, but for 99.99% practical code this definition is enough. And so, metaclasses have been invented to describe such definitions in the code.

The syntax is still at the discussion stage, but here’s how the interface metaclass can be implemented:

$class interface { constexpr { compiler.require($interface.variables().empty(), " -  !"); for (auto f : $interface.functions()) { compiler.require(!f.is_copy() && !f.is_move(), "    ; " "virtual clone()  "); if (!f.has_access()) f.make_public(); //    ! compiler.require(f.is_public(), // ,   "interface functions must be public"); f.make_pure_virtual(); //     } } //     ++    , //       virtual ~interface() noexcept { } }; 

The code is intuitive - we declare an interface metaclass in which at the stage of compiling the code (the constexpr block) certain checks and modifications of the final class will be carried out, which will pretend to be considered an interface.

This case can now be applied like this:

 interface Shape { int area() const; void scale_by(double factor); }; 

True, very similar to C # or Java? When compiling, the compiler will apply the metaclass interface to Shape, which will give us a class at the output:

 class Shape { public: virtual int area() const =0; virtual void scale_by(double factor) =0; virtual ~Shape() noexcept { }; }; 

Plus will generate a compilation error when attempting to add member data.
At the same time pay attention, there is no longer any "meta-magic" in the Shape class thus obtained. It's just a class, exactly the same as if it was written by hand - you can create copies of it, you can inherit from it, etc.

This is how we were able to add a new entity to the language and use it without resorting to the need to edit the standard of the language or the compiler.
Let's now define a class that could be used in ordered containers. For example, in practice you have to write the classic storage point in an ordered-container like this:

 class Point { int x = 0; int y = 0; public: Point() = default; friend bool operator==(const Point& a, const Point& b) { return ax == bx && ay == by; } friend bool operator< (const Point& a, const Point& b) { return ax < bx || (ax == bx && ay < by); } friend bool operator!=(const Point& a, const Point& b) { return !(a == b); } friend bool operator> (const Point& a, const Point& b) { return b < a; } friend bool operator>=(const Point& a, const Point& b) { return !(a < b); } friend bool operator<=(const Point& a, const Point& b) { return !(b < a); } }; 

But if at the compilation stage we have a reflection that allows us to enumerate the data members and add new methods to the class - we can put all these comparisons in the metaclass:

 $class ordered { constexpr { if (! requires(ordered a) { a == a; }) -> { friend bool operator == (const ordered& a, const ordered& b) { constexpr { for (auto o : ordered.variables()) // for each member -> { if (!(aoname$ == b.(o.name)$)) return false; } } return true; } } if (! requires(ordered a) { a < a; }) -> { friend bool operator < (const ordered& a, const ordered& b) { for (auto o : ordered.variables()) -> { if (aoname$ < b.(o.name)$) return true; if (b.(o.name$) < aoname$) return false; ) } return false; } } if (! requires(ordered a) { a != a; }) -> { friend bool operator != (const ordered& a, const ordered& b) { return !(a == b); } } if (! requires(ordered a) { a > a; }) -> { friend bool operator > (const ordered& a, const ordered& b) { return b < a ; } } if (! requires(ordered a) { a <= a; }) -> { friend bool operator <= (const ordered& a, const ordered& b) { return !(b < a); } } if (! requires(ordered a) { a >= a; }) -> { friend bool operator >= (const ordered& a, const ordered& b) { return !(a < b); } } } }; 

What? Looks hard? Yes, but you will not write such a metaclass - it will be in the standard library or in something like Boost. In your code, you only define a point, like this:

 ordered Point { int x; int y; }; 

And it will work!

In the same way, we can finally ensure that things like a pair or tuple are defined trivially:

 template<class T1, class T2> literal_value pair { T1 first; T2 second; }; 

Look, for the sake of interest, how a banal couple is defined now .

From the opening opportunities the eyes scatter:


Meta-level is very cool! True?

Here's another video for you to eat, where the Coat of Arms tells more about this:


And here is an online compiler in which you can even try it .

Source: https://habr.com/ru/post/334284/


All Articles