C++26 Core Language: Kleine Verbesserungen​

Es gibt weitere kleine Verbesserungen in C++26, darunter Pack-Indizierung, GrĂĽnde fĂĽr delete und Erweiterung von static assert.

In Pocket speichern vorlesen Druckansicht 26 Kommentare lesen
HolzwĂĽrfel mit dem Schriftzug C++

(Bild: SerbioVas/Shutterstock)

Lesezeit: 4 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Für C++26 sind neben den großen Ergänzungen Reflection und Contracts, die ich in einigen Beiträgen vorgestellt habe, auch ein paar kleinere, nützliche Ergänzungen vorgesehen.

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++.

Letzte Woche habe ich bereits Platzhaltern und erweiterten Zeichensatz gezeigt, und dieses Mal geht es um zusätzliche Ergänzungen für den C++-Standard.

Zunächst einmal ist hier die Syntax von static_assert in C++11:

static_assert(compile time predicate, unevaluated string)

In C++26 kann der String ein benutzerdefinierter Datentyp mit den folgenden Eigenschaften sein:

  • Has a size() method that produces an integer
  • Has a data() method that produces a pointer of character type such that
  • The elements in the range [data(), data()+size()) are valid. (p2741r3)

static_assert kann jetzt mit einem Formatierungsstring verwendet werden. Hier ist ein schönes Beispiel aus dem Proposal p2741r3. Ich habe daraus ein komplettes Programm gemacht.

// static_assert26.cpp

#include <iostream>
#include <format>

template <typename T, auto Expected, unsigned long Size = sizeof(T)>
constexpr bool ensure_size() {
    static_assert(sizeof(T) == Expected, "Unexpected sizeof");
    return true;
}

struct S {
    int _{};
};

int main() {

    std::cout << std::boolalpha << "C++11\n";
    static_assert(ensure_size<S, 1>()); 

    std::cout << std::boolalpha << "C++26\n";
    static_assert(sizeof(S) == 1,
        std::format("Unexpected sizeof: expected 1, got {}", sizeof(S))); 

    std::cout << '\n';    

}

Das Template ensure_size ist so definiert, dass es drei Parameter akzeptiert: einen Datentyp T, eine erwartete Größe Expected und einen optionalen Parameter Size, der standardmäßig der Größe von T entspricht. Innerhalb der Funktion überprüft eine static_assert-Anweisung, ob die Größe von T gleich Expected ist. Wenn die Größen nicht übereinstimmen, schlägt die Kompilierung mit der Meldung Unexpected sizeof fehl. Die Funktion gibt true zurück, wenn die Zusicherung erfüllt ist.

Das Programm definiert dann eine einfache Struktur S, die ein einzelnes Integer-Element _ enthält. Diese Struktur wird verwendet, um die static_assert-Funktionalität zu demonstrieren.

In der main-Funktion gibt das Programm zunächst C++11 mit std::boolalpha in der Konsole aus, um boolesche Werte als true oder false zu formatieren. Anschließend wird static_assert mit aufgerufen, wodurch überprüft wird, ob die Größe von S 1 Byte beträgt. Da die Größe von S tatsächlich größer als 1 Byte ist, schlägt diese Zusicherung fehl und führt zu einem Kompilierungsfehler.

Als Nächstes gibt das Programm C++26 in der Konsole aus und verwendet eine weitere static_assert. Dieses Mal kommt std::format zum Einsatz. Wenn die Größe von S nicht 1 Byte, sondern 4 beträgt, schlägt die Kompilierung fehl.

Der Blick auf die GCC-Fehlermeldungen zeigt drei Fehler. std::format ist bisher nicht constexpr.

Die Pack-Indizierung ist wohl die Lieblings-Template-Verbesserung fĂĽr die Freunde der Template-Metaprogrammierung.

Das folgende Beispiel basiert auf dem Proposal P2662R3.

// packIndexing.cpp

#include <iostream>
#include <string>

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

int main() {

    std::cout << '\n';

    using namespace std::string_literals;

    std::string hello = first_plus_last("Hello"s, "world"s, "goodbye"s, "World"s); 
    std::cout << "hello: " << hello << '\n';


    constexpr int sum = first_plus_last(1, 2, 10);
    std::cout << "sum: " << sum << '\n';

    std::cout << '\n';
  
}

Das bereitgestellte Beispiel ist ein Funktions-Template, das die Summe des ersten und des letzten Elements eines Parameter-Packs berechnet.

Die Funktion ist als Template definiert, das eine variable Anzahl von Parametern eines beliebigen Datentyps T akzeptiert. Der RĂĽckgabetyp der Funktion wird mithilfe einer nachgestellten RĂĽckgabetyp-Syntax angegeben.

Der Funktionskörper gibt die Summe des ersten und letzten Elements des Parameter-Packs zurück. Der Ausdruck values...[0] greift auf das erste Element zu und values...[sizeof...(values)-1] greift auf das letzte Element zu.

Hier ist die Ausgabe des Programms:

Mit C++26 kann man einen Grund fĂĽr ein delete angeben. Ich gehe davon aus, dass sich das als Best Practice durchsetzen wird. Das folgende Programm soll das Vorgehen verdeutlichen.

// deleteReason.cpp

#include <iostream>


void func(double){}

template <typename T>
void func(T) = delete("Only for double");

int main(){

    std::cout << '\n';

    func(3.14);
    func(3.14f);
  
    std::cout << '\n';

}

Die Funktion func ist auf zwei Arten überladen. Die erste Überladung ist eine reguläre Funktion, die ein double als Parameter verwendet. Diese Funktion kann ohne Probleme mit einem double-Argument aufgerufen werden.

Die zweite Überladung ist ein Template, das jeden Datentyp als Parameter verwenden kann. Diese Funktion wird jedoch explizit mit dem = delete-Spezifizierer mit der benutzerdefinierten Meldung „Only for double“ gelöscht. Das bedeutet, dass jede Instanziierung mit einem anderen Datentyp als double zu einem Kompilierungsfehler führt und die bereitgestellte Meldung angezeigt wird.

In der main-Funktion ruft das Programm func mit dem Argument 3.14 auf, das ein double ist. Dieser Aufruf ist gĂĽltig und ruft die Nicht-Template-Ăśberladung von func auf.

Als Nächstes versucht das Programm, func mit dem Argument 3.14f aufzurufen, das ein float ist. Da es keine Nicht-Template-Überladung von func gibt, die einen float akzeptiert, würde die Template-Funktion instanziiert werden. Da die Template-Funktion jedoch für alle Datentypen außer double gelöscht wurde, führt dieser Aufruf zu einem Kompilierungsfehler mit der Meldung „Only for double“.

In meinem nächsten Artikel werde ich auf die C++26-Bibliothek eingehen. (rme)