C++Guns – RoboBlog

01.09.2017

C++ Guns - Semantik und concepts - Part 1

Filed under: Allgemein — Tags: — Thomas @ 10:09

Ich lese gerade das Paper
Concepts: The Future of Generic Programming
or
How to design good concepts and use them well
von Bjarne Stroustrup

http://www.stroustrup.com/good_concepts.pdf

und muss sagen, ich bin restlos begeistert! Die Idee dahinter wird sehr toll beschrieben. Das ist mir am wichtigsten, so dass es möglich ist, die Idee auch bei anderen Programmiersprachen anzuwenden. Natürlich mehr oder weniger schlecht, da die Sprachmittel fehlen. Ich arbeite gerade an einem Beispiel aus meiner Domain welche mit concepts realisiert ist, aber das dauert noch etwas.

Deswegen gibt es heute nur ein einfaches Beispiel aus der angewandten Mathematik, welche den Begriff der Semantik verdeutlichen soll.

Ein Dreieck hat bekanntlich drei Ecken. Also drei Punkte. Auch wenn es nervt, dieser Witz ist sehr treffend. Viele Computerformate bilden ein Dreick als Polygonlinie mit vier Punkten ab! Deren Computer würde explodieren, würde sie C++ mit concepts nutzen 😉

Also, ein Dreieck im 3D Raum hat drei Punkte und jeder Punkt hat drei Skalare: x, y, und z. Es soll der Normalenvector zu einem Dreieck berechnet werden. Dieser steht senkrecht zur Fläche, welche die drei Punkte aufspannen.

Punkte und Dreieck kann man sich leicht vorstellen und anfassen. Wir können sie also schnell als Typen in C++ implementieren:

#ifndef __cpp_aggregate_bases
    #error "You need an actual C++17 compiler with feature P0017R0 'Extension to aggregate initialization' for this example"
#endif
#include <iostream>
#include <array>

using namespace std;

struct Point3D : public array<double, 3> {
};

struct Triangle {
    array<Point3D, 3> points;
};

auto operator-(const Point3D& p1, const Point3D& p2) {
    return Point3D{ p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2] };
}

Laut Internet berechnet sich der Normalvekor so:

Das Kreuz-Produkt zweier Vektoren steht senkrecht auf der Fläche, das die beiden Vektoren aufspannen.

Das Kreuzprodukt schütteln wir natürlich im Schlaf aus dem Ärmel. Das haben wir in der Schule rauf und runter geübt. Also los:

auto normalenVector(const Triangle& tri) {
    const auto vec1 = tri.points[1]-tri.points[0];
    const auto vec2 = tri.points[2]-tri.points[0];

    return Point3D{ vec1[1]*vec2[2]-vec1[2]*vec2[1],
                vec1[2]*vec2[0]-vec1[0]*vec2[2],
                vec1[0]*vec2[1]-vec1[1]*vec2[0] };
}

Das ist immer etwas Tipparbeit aber einmal implementiert, immer anwendbar. Oder?

Jetzt fehlt nur noch das Hauptprogramm mit einem kleinen Test:

ostream& operator<<(ostream& s, const Point3D& p) {
    s << "(" << p[0] << " " << p[1] << " " << p[2] << ")";
    return s;
}

int main() {
    Triangle tri{ Point3D{0, 0, 0}, Point3D{10, 0, 0}, Point3D{10, 10, 0} };
    Point3D normal = normalenVector(tri);
    cout << normal << '\n';
}

Compilieren und Ausführen...

Starting part1...
(0 0 100)

Alles klar. Das Ergebnis ist auch richtig. Sind wir fertig? Wo sind die Concepts? Nagut die kommen noch. Aber wo ist der Semantik Fehler? Die Funktion normalenVector() soll einen Vektor zurück geben. Aber wir haben als Rückgabetyp ein Punkt implementiert. Technisch lassen sich sowohl Punkt als auch Vector als drei Skalare implementieren. Aber ihre Bedeutung ist eine ganz andere! Ein Punkt ist eine Position im Raum. Ein Vektor zeigt in eine Richtung.

Jetzt kommt der erste Schritt hin zu Concepts. Die eben erkannte Bedeutung formulieren wir nun im Code. Dazu erstellen wir einen neuen Typ der einen Vektor repräsentiert und die Funktion normalenVector() gibt einen Vektortyp zurück:

struct Vector3D : public array<double, 3> {
};
...
return Vector3D{ ... };

Das neue Programm wäre funktional gleich dem alten. Sogar der Assembler und Binärcode wäre 100% identisch. Das Rechenergebnis natürlich auch. Der Unterschied besteht darin, dass der Compiler nun prüfen kann, ob das, was wir in unserem Programm zum Ausdruck bringen wollen, auch wirklich implementiert haben. Haben wir nicht:

main.cpp: In function ‘int main()’:
main.cpp: error: conversion from ‘Vector3D’ to non-scalar type ‘Point3D’ requested
Point3D normal = normalenVector(tri);

Na, das schaut für den Anfang gar nicht mal so schlecht aus. Das ist nur ein kleiner Schritt Richtung concepts, der sogar in FORTRAN realisierbar ist.

Im nächsten Teil portiere ich diese Idee nach FORTRAN um zu zeigen, wo die Grenze dieser Sprache liegt. Um dann im übernächsten Teil endlich Richtung concepts zu starten.

conceptpart1.gz

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress