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.
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. WennaundbZeiger sind, mĂŒssen sie auf dasselbe Array verweisen.std::lerp(a, b, t)berechnet die lineare Interpolation(a + t(b - a)). FallstauĂ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 [1] 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 [2] [- [3]-] Keine dynamische Speicherallokation notwendig [4].
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 [5] (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 [6] verwenden.
Wie geht's weiter?
In meinem nÀchsten Artikel stelle ich die Erweiterungen der chrono [7]-Bibliothek in C++20 vor: Tageszeit, Kalender und Zeitzonen. ( [8])
URL dieses Artikels:
https://www.heise.de/-4930612
Links in diesem Artikel:
[1] https://godbolt.org/z/Y8qsbz
[2] https://www.grimm-jaud.de/index.php/blog/keine-dynamische-speicherallokation-mit-std-array
[3] https://www.grimm-jaud.de/index.php/blog/keine-dynamische-speicherallokation-mit-std-array
[4] https://www.grimm-jaud.de/index.php/blog/keine-dynamische-speicherallokation-mit-std-array
[5] https://en.wikipedia.org/wiki/C++_Technical_Report_1
[6] https://godbolt.org/z/bhY3MW
[7] https://en.cppreference.com/w/cpp/chrono
[8] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien