Noch mehr praktische Werkzeuge in C++20
Mit neuen Werkzeugen in C++20 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.
- Rainer Grimm
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. Wenna
undb
Zeiger sind, müssen sie auf dasselbe Array verweisen.std::lerp(a, b, t)
berechnet die lineare Interpolation(a + t(b - a))
. Fallst
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. ()