Funktional in C++: Nahe und ferne Zukunft

Inhaltsverzeichnis[Anzeigen]

Auf welche funktionalen Feature können wir mit C++17 warten, auf welche können wir mit C++20 hoffen? Genau diese Frage wird dieser Artikel kurz und bündig beantworten.

 

Mit C++17 stehen fold expressions und der neue Container std::optional vor der Tür. Noch spannender kann C++20 werden. Concepts Lite, die Range Bibliothek und verbesserte Futures bieten ganz neue Konzepte in C++ an.

timeline.FunktionalCpp17Cpp20

Zuerst zur nahen Zukunft. Um im nächsten Artikel mit meiner Systematik zur funktionalen Programmierung mit C++ zu starten, werde ich mich in diesem Artikel kurz und bündig halten. Meine Artikel durch die funktionalen Feature von C++ soll ja nur als Aperitif dienen.

C++17

Fold Expressions

C++11 kennt Variadic Templates. Das sind Template, die eine beliebige Anzahl von Template-Parameter besitzen können. Diese beliebige Anzahl wird in dem Parameter Pack gehalten. Neu ist mir C++17, dass ein Parameter Pack direkt über einem binären Operator reduziert werden kann. Damit lassen sich die aus Haskell bekannten fold Funktionsfamilie foldl, foldr, foldl1 und foldr1, die eine Liste sukzessive auf einen Wert reduzieren, direkt in C++ umsetzen. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// foldExpression.cpp

#include <iostream>

template<typename... Args>
bool all(Args... args) { return (... && args); }

template<typename... Args>
bool any(Args... args) { return (... || args); }

template<typename... Args>
bool none(Args... args) { return not(... || args); }


int main(){
    
  std::cout << std::endl;

  std::cout << std::boolalpha;

  std::cout << "all(true): " << all(true) << std::endl;
  std::cout << "any(true): " << any(true) << std::endl;
  std::cout << "none(true): " << none(true) << std::endl;
  
  std::cout << std::endl;

  std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl;
  std::cout << "any(true, true, true, false): " << any(true, true, true, false) << std::endl;
  std::cout << "none(true, true, true, false): " << none(true, true, true, false) << std::endl;
  
  std::cout << std::endl;
  
  std::cout << "all(false, false, false, false): " << all(false, false, false, false) << std::endl;
  std::cout << "any(false, false, false, false): " << any(false, false, false, false) << std::endl;
  std::cout << "none(false, false, false, false): " << none(false, false, false, false) << std::endl;
  
  std::cout << std::endl;
  
}

 

Die Funktions-Templates all, any und  none geben zur Compilezeit true oder false zurück. Exemplarisch betrachte ich das Funktions-Template any in Zeile 5 und 6 genauer. In C++17 kann das Parameter Pack (...) direkt über einem binären Operator (... &&& args) entpackt werden. Dabei definieren die drei Punkte (Ellipse) das Parameter-Pack. 

Die Ausgabe des Programms gibt es mit dem Online-Compiler auf cppreference.com.

foldExpressions

Haskell hat die Maybe Monade, C++17 erhält std::optional.

std::optional

std::optional steht für eine Berechnung, die einen Wert enthalten kann. So muss der find-Algorithmus oder die Abfrage eines Hashtabelle damit umgehen, dass die Anfrage nicht beantwortet werden kann. Gerne werden spezielle Werte, die für das Vorhandensein keines Ergebnisses stehen (sogenannte Nicht-Ergebnisse), verwendet. Als Nicht-Ergebnis haben sich Null-Zeiger, leere Strings oder auch besondere Integer-Wert etabliert. Diese Technik ist aufwändig und fehleranfällig, da diese Nicht-Ergebnisse besonders behandelt werden müssen und sich syntaktisch nicht von einem regulären Ergebnis unterschieden. std::optional erhält im Falle eines Nicht-Ergebnisses keinen Wert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// optional.cpp

#include <experimental/optional>
#include <iostream>
#include <vector>

std::experimental::optional<int> getFirst(const std::vector<int>& vec){
  if ( !vec.empty() ) return std::experimental::optional<int>(vec[0]);
  else return std::experimental::optional<int>();
}

int main(){
    
    std::vector<int> myVec{1,2,3};
    std::vector<int> myEmptyVec;
    
    auto myInt= getFirst(myVec);
    
    if (myInt){
        std::cout << "*myInt: "  << *myInt << std::endl;
        std::cout << "myInt.value(): " << myInt.value() << std::endl;
        std::cout << "myInt.value_or(2017):" << myInt.value_or(2017) << std::endl;
    }
    
    std::cout << std::endl;
    
    auto myEmptyInt= getFirst(myEmptyVec);
    
    if (!myEmptyInt){
        std::cout << "myEmptyInt.value_or(2017):" << myEmptyInt.value_or(2017) << std::endl;
    }
   
}

 

std::optional befindet sich zum aktuellen Zeitpunkt im Namensraum experimental. Das wird sich mit C++17 ändern. Die Funktion getFirst gibt das erste Element zurück (Zeile 8), falls es existiert. Falls nicht, ein std::optional<int> Objekt (Zeile 9). In der main-Funktion kommen zwei Vektoren zum Einsatz. Die Aufrufe getFirst in Zeile 17 und 27 geben die std::optional Objekte zurück. Im Falle von myInt (Zeile 19) enthält das Objekt einen Wert, im Falle von myEmptyInt (Zeile 29) keinen Wert. Nun lässt sich der Wert von myInt (Zeile 20 - 22) ausgeben. Die Methode value_or in Zeile 22 und 30 gibt abhängig davon, ob das std::optional-Objekt einen Wert enthält, diesen oder einen Default-Wert zurück.

optinal

Der Einfluss der funktionalen Programmierung auf C++ - insbesondere in der Gestalt von Haskell - steigt deutlich mit C++20. Natürlich bewege ich mich bei der Beschreibung der neuen Feature von C++20 auf sehr dünnem Eis. Mit C++17 bin ich schon mal eingebrochen. So ging ich davon aus, dass die drei folgenden Feature bereits Bestandteil von C++17 sein werden.

C++20

 Versprochen, die Details zu Concepts Lite, der Ranges Bibliothek und den verbesserten Futures gibt es in zukünftigen Artikeln.

Concepts Lite

Typklassen in Haskell sind Interfaces für ähnliche Typen. Ist ein Typ Mitglied einer Typklasse, so bietet dieser Typ bestimmte Eigenschaften an. Typklassen erfüllen für die generische Programmierung eine ähnliche Rolle, wie Interfaces für die objektorientierte Programmierung. Inspiriert durch Typklassen, werden sich an Template-Parameter Bedingungen stellen lassen. Die neue Funktionalität läuft unter den Namen Concepts Lite. So fordert der sort Algorithmus, dass seine Template-Argumente sortierbar sein müssen.

template<typename Cont>
  requires Sortable<Cont>()
void sort(Cont& container){...}

 

Welche Vorteile bringen Concepts Lite mich sich? An erster Stelle steht in der Template-Deklaration, welche Eigenschaften das Template-Argument erfüllen muss. Damit kann der Compiler gegebenenfalls den Bruch des Vertrages mit einer eindeutigen Fehlermeldung monieren:

std::list<int> lst = {1998,2014,2003,2011};
sort(lst); // ERROR: lst is no random-access container with <

 

Mindestens so sehnsüchtig wird in der C++ Community die neue Ranges Bibliothek von Eric Niebler erwartet.

Ranges Bibliothek

Oberflächlich betrachtet erlaubt die Ranges Bibliothek, Algorithmen der Standard Template Library direkt auf dem Container auszuführen. Bei genaueren Blick, erweiterte sie C++ um vollkommen neuen Programmiertechniken.

  • Bedarfsauwertung (lazy evaluation) erlaubt Algorithmen auf unendlichen Datenströmen zu formulieren.
  • Funktionskomposition ist dank dem Pipe-Symbol (|) direkt möglich.
  • Range-Comprehension ermöglicht das direkt erzeugen von Ranges, ähnlich zu List Comprehension in Python oder Haskell.
1
2
3
4
  auto odds= view::transform([](int i){ return i*i; }) |
             view::remove_if([](int i){ return i % 2 == 0; }) |
             view::take_while([](int i){ return i < 1000; });
  auto oddNumbers= view::ints(1) | odds;

 

oddNumbers enthält als Ergebnis die Quadrate aller ungeraden Zahlen, die kleiner als 1000 sind: 1, 9, 25, ..., 841, 961. Wie funktioniert das ganze?

Zuerst zu odds. odds ist eine Funktionskomposition mit Hilfe des (|) Symbols. Die Funktionskomposition ermittelt zu jeder Zahl ihr Quadrat (Zeile 1), entfernt alle Zahlen, die gerade sind (Zeile 2) und hört dann auf, wenn die Quadratzahlen größer als 1000 sind (Zeile 3). In Zeile 4 kommt odds zu Einsatz. Dabei erzeugt view::ints(1) den unendlichen Eingabestrom an natürlichen Zahlen, der mit 1 beginnt. Dieser Eingabestrom wird durch odds beendet.

Es gibt mehrere Proposals zur Verbesserung der Futures, die mit C++11 eingeführt wurden. Der Hauptkritikpunkt an den Futures in C++11 ist es, das sie nicht komponiert werden können. Damit soll C++20 aufräumen.

Verbesserte Futures

Der Codeschnipsel gibt eine Idee, wie die Zukunft von Futures aussehen kann.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
future<int> f1= async([]() {return 123;});
future<string> f2 = f1.then([](future<int> f){ return f.get().to_string(); });

future<int> futures[] = {async([]() { return intResult(125); }), 
                         async([]() { return intResult(456); })};
future<vector<future<int>>> any_f = when_any(begin(futures), end(futures));

future<int> futures[] = {async([]() { return intResult(125); }), 
                         async([]() { return intResult(456); })};
future<vector<future<int>>> all_f = when_all(begin(futures), end(futures));

 

So gibt f1.then in Zeile 2 einen neuen Future zurück, der dann ausgeführt wird, wenn f1 mit seiner Arbeit fertig ist. Ähnliches gilt für die Futures any_f und all_f in Zeile 6 und 10. any_f wird dann ausgeführt, wenn einer der Futures in Zeile 4 und 5 fertig sind. all_f, wenn alle der Futures in Zeile 8 und 9 fertig sind.

Eine Frage bleibt natürlich noch bestehen. Was haben Futures mit funktionaler Programmierung gemein? Eine Menge! Die erweiterten Future sind eine Monade. Wer's nicht glaubt: I see a Monad in your Future (Bartosz Milewski). 

Wie geht's weiter?

Jetzt beginne ich damit, das Feld der funktionalen Programmierung in C++ systematisch aufzurollen. Los geht's im nächsten Artikel mit der Beantwortung der Frage. Was ist funktionale Programmierung?

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 


 

Kommentar schreiben


Modernes C++

Abonniere den Newsletter

Inklusive zwei Artikel meines Buchs
Introduction und Multithreading

Beiträge-Archiv

Sourcecode

Neuste Kommentare