Metaprogrammierung ist ein ziemlich exotisches Thema. Es ist interessant, darüber zu lernen, es ist ein mächtiges Werkzeug, und gelegentlich können Sie es nützlich finden. Aber es wird nie das am häufigsten verwendete Werkzeug in Ihrem Werkzeugkasten sein. Manchmal möchten Sie vielleicht, dass Ihr Code auf eine Reihe von nicht verwandten Typen mit unterschiedlichen Eigenschaften wirkt, und hier kommt die Metaprogrammierung ins Spiel. Mit ein paar Tricks können Sie eine Überladung einer Funktion schreiben, die nur verfügbar ist, wenn das Argument vom Typ Integral ist, oder wenn es ein Zeiger ist, oder wenn es entweder vom Typ X, Y oder Z ist (vielleicht ignorieren Sie die Konstante von Z).
Es ist im Wesentlichen eine Programmierung mit Typen. Normalerweise kann Ihr Programm z. B. zwei Zahlen nehmen und eine dritte Zahl erzeugen oder Ihnen sagen, ob eine Zahl eine bestimmte Anforderung erfüllt. Die Metaprogrammierung kann zwei Typen nehmen und einen dritten Typ erzeugen oder Ihnen sagen, ob ein Typ eine bestimmte Anforderung erfüllt. Und ja, sie ist wahrscheinlich vor allem bei der Entwicklung von Bibliotheken nützlich. Aber andererseits, der meiste Code könnte als Bibliothekscode betrachtet werden. Man könnte sagen, dass alles außerhalb Ihrer main()-Funktion Bibliothekscode ist.
Wenn Sie ein Problem durch Metaprogrammierung lösen wollen, werden Sie wahrscheinlich die entsprechenden Boost-Bibliotheken verwenden wollen, um die schwere Arbeit zu erledigen. Boost.TypeTraits und natürlich Boost.Mpl können die Dinge für Sie wirklich vereinfachen. Aber es ist nicht etwas, das Sie brauchen und Sie werden es wahrscheinlich nicht oft brauchen.
Generische Programmierung ist verwandt (und kann in einigen Fällen Metaprogrammierung unter der Haube verwenden, um wirklich generisch zu werden, zum Beispiel verwendet die Standardbibliothek einen Hauch von Metaprogrammierung, um rohe Zeiger in gültige Iteratoren zu verwandeln, was für das "Iterator"-Konzept erforderlich ist, um generisch zu sein), aber nicht ganz dasselbe. Und es ist viel weiter verbreitet.
Jedes Mal, wenn Sie eine Instantiierung einer std::vector
verwenden Sie eine generische Programmierung. Jedes Mal, wenn Sie ein Paar von Iteratoren verwenden, um eine Folge von Werten zu verarbeiten, verwenden Sie generische Programmierung. Bei der generischen Programmierung geht es darum, dass Ihr Code so generisch wie möglich sein sollte und unabhängig von den verwendeten Typen funktioniert. Ein std::vector erfordert nicht, dass der enthaltene Typ eine "ICanBeContained"-Schnittstelle implementiert (erinnern Sie sich daran, dass in Java alles von Object abgeleitet sein muss, damit es in einer Containerklasse gespeichert werden kann? Das bedeutet, dass primitive Typen in Boxen untergebracht werden und wir die Typsicherheit verlieren. Das ist nicht generisch, und es ist eine sinnlose Einschränkung).
Der Code zur Iteration über eine Sequenz mit Iteratoren ist generisch und funktioniert mit cualquier Typ von Iteratoren oder sogar mit einfachen Zeigern.
Die generische Programmierung ist sehr nützlich und kann OOP oft weitgehend ersetzen. (siehe das obige Beispiel. Warum sollte ich einen Container schreiben, bei dem die enthaltenen Typen eine Schnittstelle implementieren müssen, wenn ich diese Einschränkung vermeiden kann?)
Wenn Sie in der OOP Schnittstellen verwenden, geht es oft nicht darum, dass sich der Typ während der Laufzeit ändern kann (obwohl das natürlich auch ab und zu vorkommt), sondern darum, dass Sie zur Kompilierzeit einen anderen Typ einfügen können (vielleicht durch Injektion eines Mock-Objekts während der Tests, anstatt die vollwertige Implementierung zu verwenden), oder einfach um zwei Klassen zu entkoppeln. Die generische Programmierung kann dies leisten, ohne dass Sie die mühsame Arbeit der Definition und Pflege der Schnittstelle übernehmen müssen. In diesen Fällen bedeutet die generische Programmierung, dass Sie weniger Code schreiben und pflegen müssen, und Sie erhalten eine bessere Leistung und eine höhere Typsicherheit. Also ja, Sie sollten sich mit generischer Programmierung auf jeden Fall wohlfühlen. C++ ist keine sehr gute OOP-Sprache. Wenn Sie sich strikt an OOP halten wollen, sollten Sie zu Java oder einer anderen, mehr auf OOP fixierten Sprache wechseln. C++ ermöglicht Sie können OO-Code schreiben, aber das ist oft nicht die beste Lösung. Es gibt einen Grund, warum fast die gesamte Standardbibliothek auf generischer Programmierung und nicht auf OOP beruht. Es gibt nur sehr wenig Vererbung oder Polymorphismus in der Standardbibliothek. Man brauchte sie nicht, und der Code wurde ohne sie einfacher zu benutzen und leistungsfähiger.
Und um Ihre anderen Fragen zu beantworten: Ja, die generische Programmierung ist ein ziemlich eigenständiges Paradigma. Die Schablonen-Metaprogrammierung ist es nicht. Es handelt sich um eine ziemlich spezifische Technik zur Manipulation des Typsystems, mit der sich eine kleine Anzahl von Problemen sehr gut lösen lässt. Um als Paradigma zu gelten, müsste es meiner Meinung nach viel allgemeiner nützlich sein, und ein Ansatz, den man grundsätzlich für todo wie funktionale, OO- oder generische Programmierung.
Ich denke, xtofl hat es wirklich auf den Punkt gebracht: Bei der generischen Programmierung geht es darum, den Code typ-unbewusst zu machen. (Ein std::vector hat keine Pflege ou Wissensbedarf welcher Typ darin gespeichert ist. Es funktioniert einfach).
Bei der Metaprogrammierung hingegen geht es um Typberechnungen. Bei den Typen T0 und T1 können wir einen Typ T2 definieren, so wie wir bei den ganzen Zahlen N0 und N1 einen N2 definieren können, der die Summe von N0 und N1 ist.
Die Boost.Mpl-Bibliothek bietet ein offensichtliches Beispiel dafür. Wenn Sie in Ihrem normalen Code die ganzen Zahlen N0, N1 und N2 haben, können Sie einen std::vector erstellen, der diese drei Werte enthält. Ich kann dann einen anderen Algorithmus verwenden, um einen Index zu berechnen, und dann den Wert extrahieren, der an dieser Stelle im Vektor gespeichert ist.
Bei den Typen T0, T1 und T2 können wir einen mpl::Vektor erstellen, der diese drei Typen enthält Typen . Ich kann nun einen anderen Algorithmus verwenden, um einen Index zur Kompilierungszeit zu berechnen und die Typ an dieser Stelle des Vektors gespeichert.