Noch mehr praktische Werkzeuge in C++20

Modernes C++ Rainer Grimm  –  4 Kommentare

Heute werde ich weitere praktische Werkzeuge in C++20 vorstellen. Mit diesen Werkzeugen lässt sich einfach der Mittelpunkt zweier Werte berechnen, prüfen, ob ein String mit einem gegebenen String anfängt oder endet, und einfach eine aufrufbare Einheit erzeugen.

Mein Artikel beginnt arithmetisch.

Mittelpunkt und lineare Interpolation

  • std::midpoint(a, b) berechnet den Mittelpunkt (a + (b - a) / 2) zweier Integrale, zweier Gleitkommazahlen oder zweier Zeiger. Wenn a und b Zeiger sind, müssen sie auf dasselbe Array verweisen.
  • std::lerp(a, b, t) berechnet die lineare Interpolation (a + t(b - a)). Falls t außerhalb des Bereichs [0, 1] ist, berechnet die Funktion die lineare Extrapolation.

Das folgende einfache Programm wendet beide arithmetische Funktionen an:

// midpointLerp.cpp

#include <cmath> // std::lerp
#include <numeric> // std::midpoint
#include <iostream>

int main() {

std::cout << std::endl;

std::cout << "std::midpoint(10, 20): " << std::midpoint(10, 20) << std::endl;

std::cout << std::endl;

for (auto v: {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}) {
std::cout << "std::lerp(10, 20, " << v << "): " << std::lerp(10, 20, v) << std::endl;
}

}

Die Ausgabe des Programms sollte selbsterklärend sein. Falls nicht, habe ich das Programm auf dem Compiler Explorer hinterlegt.

C++20 bietet einfache Funktionen an, um Arrays zu kreieren.

Arrays erzeugen

Mit den Funktionen std::to_array und std::make_shared bietet C++20 neue Wege an, ein std::array und ein std::shared_ptr von C-Arrays zu erzeugen.

std::to_array

Dank der Funktion std::to_array ist das Erzeugen eines std::array aus einem C-Array eine einfache Aufgabe:

// toArray.cpp

#include <type_traits>
#include <utility>
#include <array>

int main(){

auto arr1 = std::to_array("C-String Literal");
static_assert(arr1.size() == 17); // (1)

auto arr2 = std::to_array({ 0, 2, 1, 3 });
static_assert(std::is_same<decltype(arr2), std::array<int, 4>>::value); // (2)

auto arr3 = std::to_array<long>({ 0, 1, 3 }); // (3)
static_assert(std::is_same<decltype(arr3), std::array<long, 3>>::value); // (3)

auto arr4 = std::to_array<std::pair<int, float>>( { { 3, .0f }, { 4, .1f }, { 4, .1e23f } });
static_assert(arr4.size() == 3); // (4)
static_assert(std::is_same<decltype(arr4), std::array<std::pair<int, float>, 3>>::value);

}

Die Zeilen (1), (2), (3) und (4) sichern zu, dass das erzeugte std::array den richtigen Typ und die richtige Länge besitzt.

Per Design ist ein std::array so billig und so schnell wie ein C-Array. Falls du mehr Hintergrundinformation zu std::array benötigst und wissen willst, warum du ein C-Array nicht verwenden sollst, kann ich meinen Artikel empfehlen: std::array [--] Keine dynamische Speicherallokation notwendig.

Darüber hinaus kennt std::array seine Größe und bietet das typische Interface eines Containers der Standard Template Library wie std::vector an.

Zum jetzigen Zeitpunkt unterstützen bereits der MSVC, Clang und GCC Compiler diese komfortable Art, ein std::array zu erzeugen. Diese Unterstützung gilt aber nicht für das nächste Feature.

Ein std::shared_ptr aus einem C-Array erzeugen

Seit C++11 besitzt C++ die Fabrikfunktion std::make_shared, um einen std::shard_ptr zu erzeugen. Mit C++20 unterstützt std::make_shared auch C-Arrays, mit denen sich std::shared_ptr von C-Arrays erzeugen lassen:

auto s1 = std::make_shared<double[]>(1024);
auto s2 = std::make_shared<double[]>(1024, 1.0);

s1 ist ein std::shared_ptr eines C-Arrays. Alle seine Mitglieder werden default-initialisiert. s2 ist ein std::shared_ptr eines C-Arrays. Jedes Mitglied erhält den Wert 1.0.

Im Gegensatz zu std::make_shared unterstützen aktuelle MSVC, Clang oder auch GCC die nächsten zwei neue Funktionen des std::string.

Prüfen, ob ein String mit einem Präfix beginnt oder einem Suffix endet

std::string erhält die neuen Funktionen starts_with und ends_with. Diese Funktionen prüfen, ob ein std::string mit einem vorgegebenen String beginnt oder endet:

// stringStartsWithEndsWith.cpp

#include <iostream>
#include <string_view>
#include <string>

template <typename PrefixType>
void startsWith(const std::string& str, PrefixType prefix) {
std::cout << " starts with " << prefix << ": "
<< str.starts_with(prefix) << '\n'; // (1)
}

template <typename SuffixType>
void endsWith(const std::string& str, SuffixType suffix) {
std::cout << " ends with " << suffix << ": "
<< str.ends_with(suffix) << '\n';
}

int main() {

std::cout << std::endl;

std::cout << std::boolalpha;

std::string helloWorld("Hello World");

std::cout << helloWorld << std::endl;

startsWith(helloWorld, helloWorld); // (2)

startsWith(helloWorld, std::string_view("Hello")); // (3)

startsWith(helloWorld, 'H'); // (4)

std::cout << "\n\n";

std::cout << helloWorld << std::endl;

endsWith(helloWorld, helloWorld);

endsWith(helloWorld, std::string_view("World"));

endsWith(helloWorld, 'd');

}

Beide Funktionen starts_with und ends_with sind Prädikate. Das heißt, dass sie einen Wahrheitswert zurückgeben. Die Funktion starts_with (Zeile 1) kann mit einem std::string (Zeile 2), einem std::string_view (Zeile 3) und einem char (Zeile 4) aufgerufen werden.

Das nächste praktische Werkzeug in C++20 mag dich verwundern.

std::bind_front

std::bind_front (Func&& func, Args&& ... args) erzeugt einen aufrufbaren Wrapper für eine aufrufbare Einheit func. std::bind_front kann beliebige viele Argumente annehmen und bindet diese vorne.

Nun möchte ich über den verwunderlichen Punkt schreiben. Mit C++11 besitzt C++ std::bind und Lambda-Ausdrücke. Um ganz pedantisch zu sein, std::bind gibt es bereits seit dem Technical Report 1 (TR1). Beide können als Ersatz für std::bind_front verwendet werden. Es geht noch weiter, std::bind_front lässt sich als kleiner Bruder von std::bind auffassen, den nur std::bind unterstützt das Umordnen der Argumente. Natürlich gibt es einen Grund in der Zukunft, std::bind_front std::bind vorzuziehen: std::bind_front propagiert Ausnahmespezifikationen des zugrunde liegenden Aufrufoperators.

Das folgende Programm bringt auf den Punkt, dass sich mit std::bind oder Lambda-Ausdrücke ähnliche Anwendungsfälle wie mit std::bind_front umsetzen lassen:

// bindFront.cpp

#include <functional>
#include <iostream>

int plusFunction(int a, int b) {
return a + b;
}

auto plusLambda = [](int a, int b) {
return a + b;
};

int main() {

std::cout << std::endl;

auto twoThousandPlus1 = std::bind_front(plusFunction, 2000); // (1)
std::cout << "twoThousandPlus1(20): " << twoThousandPlus1(20) << std::endl;

auto twoThousandPlus2 = std::bind_front(plusLambda, 2000); // (2)
std::cout << "twoThousandPlus2(20): " << twoThousandPlus2(20) << std::endl;

auto twoThousandPlus3 = std::bind_front(std::plus<int>(), 2000); // (3)
std::cout << "twoThousandPlus3(20): " << twoThousandPlus3(20) << std::endl;

std::cout << "\n\n";

using namespace std::placeholders;

auto twoThousandPlus4 = std::bind(plusFunction, 2000, _1); // (4)
std::cout << "twoThousandPlus4(20): " << twoThousandPlus4(20) << std::endl;

auto twoThousandPlus5 = [](int b) { return plusLambda(2000, b); }; // (5)
std::cout << "twoThousandPlus5(20): " << twoThousandPlus5(20) << std::endl;

std::cout << std::endl;

}

Jeder Aufruf (Zeilen 1 bis 5) erhält eine aufrufbare Einheit, die zwei Argumente annimmt und gibt eine aufrufbare Einheit zurück, die nur noch ein Argument benötigt, da das erste Argument bereits auf 2000 gesetzt ist. Die aufrufbare Einheit ist eine Funktion (Zeile 1), ein Lambda-Ausdruck (Zeile 2) oder ein vordefiniertes Funktionsobjekt (Zeile 3). _1 ist ein sogenannter Platzhalter (Zeile 4), der in diesem Fall für das fehlende Argument steht. Mit einem Lambda-Ausdruck (Zeile 5) lässt sich direkt das erste Argument setzen und b für den fehlenden Parameter verwenden. Vom Standpunkt der Lesbarkeit betrachtet, sollte std::bind_front deutlich eingehender als std::bind oder der Lambda-Ausdruck sein.

In bekannter Manier lässt sich das Beispiel auf dem Compiler Explorer verwenden.

Wie geht's weiter?

In meinem nächsten Artikel stelle ich die Erweiterungen der chrono-Bibliothek in C++20 vor: Tageszeit, Kalender und Zeitzonen.