Ein Überblick über C++26: die Kernsprache​

Einen Artikel über die Zukunft zu schreiben, ist immer eine Herausforderung, da der Design-Freeze von C++26 im ersten Quartal 2025 stattfindet.​

In Pocket speichern vorlesen Druckansicht 36 Kommentare lesen

(Bild: SergioVas/Shutterstock)

Lesezeit: 3 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Dieses Bild bietet einen ersten Eindruck davon, wie C++26 aufgebaut ist.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Das Bild soll nur einen ersten Eindruck davon vermitteln, wie C++26 aussieht. Ich werde es in den nächsten Monaten, in denen ich über C++26 schreibe, bei Bedarf anpassen. Zum Beispiel haben die drei leistungsstarken Features Reflection, Contracts und std::execution einen großen Schritt in Richtung ihrer Standardisierung gemacht. Im Gegensatz dazu ignoriere ich absichtlich Pattern Matching auf dem Bild.

Sehr wirkungsvolle Features wie Reflection oder Contracts setzen die agile Idee des Minimum Viable Product um:

"Ein Minimum Viable Product (MVP) ist eine Version eines Produkts mit gerade genug Features, um von frühen Kunden genutzt werden zu können, die dann Feedback für die zukünftige Produktentwicklung geben können."

Das bedeutet, dass C++26 nur der Ausgangspunkt für Features wie Reflection oder Contracts ist. Wenn möglich, zeige ich die Features in Aktion. Viele Features sind bereits in den brandneuen C++-Compilern implementiert. Für den Rest hoffe ich auf Prototyp-Implementierungen.

Ich beginne mit der Kernsprache.

Reflection

Reflection ist die Fähigkeit eines Programms, seine Struktur und sein Verhalten zu untersuchen, zu betrachten und zu ändern. Dadurch wird die Compilezeit-Programmierung in C++ viel leistungsfähiger. Ich möchte euch in diesem Artikel nicht mit zu viel Theorie langweilen. Deshalb zeige ich euch mein Lieblingsbeispiel aus dem Reflection-Proposal P2996R5.

Eine Frage, die ich in meinen Kursen oft beantworten muss, lautet: Wie kann ich einen Enumerator in einen String umwandeln?

// enumString.cpp

#include <iostream>
#include <experimental/meta>
#include <string>
#include <type_traits>

// start 'expand' definition
namespace __impl {
  template<auto... vals>
  struct replicator_type {
    template<typename F>
      constexpr void operator>>(F body) const {
        (body.template operator()<vals>(), ...);
      }
  };

  template<auto... vals>
  replicator_type<vals...> replicator = {};
}

template<typename R>
consteval auto expand(R range) {
  std::vector<std::meta::info> args;
  for (auto r : range) {
    args.push_back(std::meta::reflect_value(r));
  }
  return substitute(^__impl::replicator, args);
}
// end 'expand' definition

template<typename E>
  requires std::is_enum_v<E>                      // (1)
constexpr std::string enum_to_string(E value) {
  std::string result = "<unnamed>";
  [:expand(std::meta::enumerators_of(^E)):] >>    // (2)
  [&]<auto e>{
    if (value == [:e:]) {
      result = std::meta::identifier_of(e);       // (3)
    }
  };
  return result;
}


int main() {

    std::cout << '\n';

    enum Color { red, green, blue };
    std::cout << "enum_to_string(Color::red): " << enum_to_string(Color::red) << '\n';
    // std::cout << "enum_to_string(Color(42)): " << enum_to_string(42) << '\n'; 

    std::cout << '\n';

}

In diesem Beispiel werden die experimentellen Features (std::meta) des Standards verwendet. Hier ist die Ausgabe des Programms:

Ich möchte kurz auf das Funktions-Template enum_to_string eingehen. Die Funktion expand ist nur ein Workaround. Der Funktionsaufruf enum_to_string(Color(42)) bricht ab, weil die Funktion eine Enumeration erfordert: requires std::is_enum_v<E> (Zeile 1).

Zeile (1) wendet einen Reflexionsoperator (^E) an und ruft die Metafunktion enum_to_std::meta::enumerators_of(^E) auf. Schließlich erzeugt der sogenannte Splicer ([:refl:]) in Zeile (2) grammatikalische Elemente für die Reflexion. Die zweite Metafunktion in Zeile (3) erstellt die Zeichenfolge: std:meta::identifier_of(e)). Die Metafunktionen werden zur Compilezeit ausgeführt, ebenso wie die Reflexion.

Contracts

Ein Contract legt Schnittstellen für Softwarekomponenten genau und überprüfbar fest. Diese Softwarekomponenten sind Funktionen, die Vorbedingungen, Nachbedingungen und Invarianten erfüllen.

Hier ist ein einfaches Beispiel aus dem Proposal P2900.

int f(const int x)
  pre (x != 1) // a precondition assertion
  post(r : r != 2) // a postcondition assertion; r refers to the return value of f
{
  contract_assert (x != 3); // an assertion statement
  return x;
} 

Die Funktion f hat eine Vorbedingung, eine Nachbedingung und eine Invariante. Die Vorbedingung wird vor dem Funktionsaufruf überprüft, die Nachbedingung nach dem Funktionsaufruf und die Invariante genau zum Zeitpunkt des Aufrufs.

Der Aufruf der Funktion mit den Argumenten 1, 2 oder 3 führt zu einer Vertragsverletzung. Es gibt verschiedene Möglichkeiten, auf eine Vertragsverletzung zu reagieren.

void g()
{
  f(0); // no contract violation
  f(1); // violates precondition assertion of f
  f(2); // violates postcondition assertion of f
  f(3); // violates assertion statement within f
  f(4); // no contract violation
}

Die Kernsprache von C++26 hat neben Reflexion und Contracts noch mehr zu bieten. Ich möchte sie kurz nennen und einen Codeausschnitt aus den entsprechenden Vorschlägen präsentieren.

  • Platzhalter und erweiterter Zeichensatz
auto [x, y, _] = f();​

Der Unterstrich (_) steht für "I don't care." und kann mehrmals verwendet werden.

  • static_assert-Erweiterung
static_assert(sizeof(S) == 1,
    std::format("Unexpected sizeof: expected 1, got {}", sizeof(S))

  • Template-Verbesserungen

Es gibt viele Verbesserungen an Templates in C++26. Mein Favorit ist die Pack-Indizierung:

template <typename... T>
constexpr auto first_plus_last(T... values) -> T...[0] {
  return T...[0](values...[0] + values...[sizeof...(values)-1]);
}

int main() {
  //first_plus_last(); // ill formed
  static_assert(first_plus_last(1, 2, 10) == 11);
}
  • delete with reason
delete("Should have a reason");

In meinem nächsten Artikel werde ich einen Überblick über die C++26-Bibliothek geben.

(mai)