Concepts - Placeholders

Inhaltsverzeichnis[Anzeigen]

C++11 kennt mit auto unconstrained placeholders. Concepts in C++20 können als constrained placeholders verwendet werden. Was sich auf den ersten Blick nicht besonders spannend anhört, ist für mich der entscheidende Quantensprung, Templates als einfaches C++ Sprachmittel zu etablieren.

 

Bevor ich die neue Syntax beschreibe, habe ich zwei Anmerkungen. Zum einen, nach der Recherche zu diesem Artikel und meinen Experimenten mit unconstrained und constrained placeholders bin ich sehr voreingenommen. Daher kann es sein, dass dieser Artikel nicht ganz objektiv ist. Zum anderen, gefallen mir die Ausdrücke unconstrained und constrained placeholders nicht in einem deutschen Artikel. Daher werde ich im weiteren Artikel von uneingeschränkten und eingeschränkten Platzhaltern sprechen.

Ein immer und immer wiederkehrende Frage

Nun geht es aber los mit dem Artikel. In Schulungen werde ich immer wieder gefragt: Wann ist eine Programmiersprache einfach? Die Antwort kann natürlich nicht sein, wenn sie es schafft, für schwierige Probleme einfach Lösungen anzubieten. Das kann nicht funktionieren.

Für mich ist eine Programmiersprache einfach, wenn sie auf möglichst wenigen Prinzipien basiert - ich nenne sie rote Fäden - und sich die Features der Programmiersprache aus diesen Prinzipien ableiten lassen. In diesem Sinne ist Python eine einfache Programmiersprache, den wer das Bilden eines Slices auf einer Sequenz verstanden hat, der kann dieses Prinzip in vielen Situationen anwenden:

slice

So folgt die Syntax den gleichen Prinzipien, unabhängig davon, ob ich jedes dritte Element einer gerade erzeugten Liste range(0,10,3), eines Strings, einer Liste oder eines Tupels ausgebe. Das Prinzip gilt auch, falls ich jedes zweite Element einer direkt erzeugten Liste range(9,0,-2), eines Strings, einer Liste oder eines Tupels rückwärts ausgebe.

In diesem Sinne war C++98 keine einfache Sprache. In diesem Sinne ist C++11 eine deutlich einfachere Sprache. So gilt nun zum Beispiel die Regel, dass sich alles mit geschweiften Klammern ( {}-Initialisierung )initialisieren lässt. Natürlich besitzt selbst C++14 noch viele Features, die nicht besonders rund wirken. Einer meiner Favoriten ist eine verallgemeinerte Lambda-Funktion.

 

1
2
3
4
5
6
auto genLambdaFunction= [](auto a, auto b) { return a < b; };

template <typename T, typename T2>
auto genFunction(T a, T2 b){
  return a < b;
}

 

So wird aus der verallgemeinerten Lambda-Funktion in Zeile 1 durch die Verwendung des Platzhalters auto für a und b auf magische Weise ein Funktions-Template. (Ich weiß, tatsächlich ist genLambdaFunction ein Funktions Objekt, das einen mit zwei Typ-Parametern parametrisierten Aufrufoperator besitzt.). Zwar ist die Funktion genFunction auch ein Funktions-Template. Leider lässt sich nicht einfach direkt mit auto definieren. Hierzu ist schon deutlich mehr Syntax in den Zeilen 3 und 4 notwendig. Syntax, an der viele Anwender oft scheitern.

Genau mit dieser Asymmetrie räumt die Platzhalter Syntax auf und damit besitzen wir in C++ wieder ein mächtiges, einfaches Prinzip und die Sprache C++ wird gemäß meiner Definition einfacher.

Platzhalter

Es gibt uneingeschränkte und eingeschränkte Platzhalter. Ein uneingeschränkter Platzhalter ist das Schlüsselwort auto, den eine mit auto definierte Variable kann jeden Typ annehmen. Ein eingeschränkter Platzhalter ist ein Concept, den es erklärt nur Variablen, die dem Typ des Concepts genügen. Im Artikel Concepts Lite habe ich Concepts mit Hilfe von Haskells Typklassen eingeführt. Dafür habe ich internationalen Lob und Tadel erhalten.

Bevor ich Platzhalter genauer vorstelle, will ich erst ein einfaches Concept definieren und anwenden.

Ein einfaches Concept

Dank des Concepts Integral müssen die Argumente meines gcd Algorithmus ganze Zahlen sein.

 

 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
// conceptsIntegral.cpp

#include <type_traits>
#include <iostream>

template<typename T>
concept bool Integral(){
  return std::is_integral<T>::value;
}

template<typename T>
requires Integral<T>()
T gcd(T a, T b){
  if( b == 0 ){ return a; }
  else{
    return gcd(b, a % b);
  }
}

int main(){

  std::cout << std::endl;

  std::cout << "gcd(100, 10)= " <<  gcd(100, 10)  << std::endl;
  std::cout << "gcd(100, 33)= " << gcd(100, 33) << std::endl;
  // std::cout << "gcd(5.5, 4,5)= " << gcd(5.5, 4.5) << std::endl;

  std::cout << std::endl;

}

 

In Zeile 6 definiere ich das Concept Integral. Das Concept Integral evaluiert dann zu true, wenn für Typ-Parameter T das Prädikat std::is_integral<T>::value true ergibt. std::is_integral<T> ist eine Funktion, der Type-Traits Bibliothek. Die Funktionen der Type-Traits Bibliothek erlauben unter anderem, ihrem Typen zur Compilezeit zu evaluieren. Die Details dazu gibt es in meinen Artikel zu der Type-Traits Bibliothek. Unter anderem habe ich bereits die Funktionen der Type-Traits Bibliothek angewandt, um den gcd Algorithmus immer Typ-sicherer zu machen: Immer Sicherer. In Zeile 12 kommt das Concept zum Einsatz. Ich werde in meinem nächsten Artikel darüber schreiben, wie sich insbesondere die Anwendung eines Concepts deutlich angenehmer formulieren lässt, so dass die Grenzen zwischen der Definition eins Funktion-Templates und einer Funktion harmonisch ineinander übergehen.

Nun aber zurück zu dem kleinen Beispiel. Dank einem aktuellen GCC 6.3 Compiler und dem Flag -fconcepts lässt sich das Programm bereits in Anwendung bewundern.

conceptsIntegral

Was passiert nun, wenn ich die Zeile 26 verwende? Das Concept schlägt zu.

conceptsIntegralError

Nun aber zurück zu den Platzhaltern. Genauer gesagt, eingeschränkten und uneingeschränkten Platzhaltern.

Eingeschränkte und uneingeschränkte Platzhalter

Eingeschränkte Platzhalter (Concepte) lassen sich überall da verwenden, an den uneingeschränkte Platzhalter (auto) zum Einsatz kommen können. Wenn das keine intuitive Regel ist?

 

 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
// conceptsPlaceholder.cpp

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T>
concept bool Integral(){
  return std::is_integral<T>::value;
}

Integral getIntegral(auto val){
  return val;
}

int main(){
  
  std::cout << std::boolalpha << std::endl;
  
  std::vector<int> myVec{1, 2, 3, 4, 5};
  for (Integral& i: myVec) std::cout << i << " ";
  std::cout << std::endl;  

  Integral b= true;
  std::cout << b << std::endl;
  
  Integral integ= getIntegral(10);
  std::cout << integ << std::endl;
  
  auto integ1= getIntegral(10);
  std::cout << integ1 << std::endl;
  
  std::cout << std::endl;

}

 

Der Einfachheit halber verwende ich wieder das Concept Integral in Zeile 7 - 10. So iterierte meine range-basierte for-Schleife in Zeile 21 über Ganzzahlen, so muss der von Integral in Zeile 24 abgeleitete Typ eine Ganzzahl sein. Weiter geht es in Zeile 27 und 30. In Zeile 27 fordere ich, dass der Rückgabetyp von getIntegral(10) dem Concept Integral genügen muss. Nicht ganz so eng sehe ich es in Zeile 30. Hier bin ich mit einem uneingeschränkten Platzhalter zufrieden.

Zum Abschluss wie immer die Ausgabe des Programms. Schön ist zu sehen. Concept verhalten sich vollkommen intuitive.

conceptsPlaceholder

Das war es schon für diesen Artikel. Natürlich nicht!  Ein neues Feature im Zusammenhang mit Platzhalter habe ich heimlich eingeführt, ohne dass es vermutlich die meisten von euch bemerkt haben. Was ist das für eine komische Funktion getIntegral in Zeile 10:

 

Integral getIntegral(auto val){
  return val;
}

 

Das Concept Integral als Rückgabetyp ist ja noch leicht verdauliche Syntax, denn seit C++14 können wir uneingeschränkte Platzhalter als Rückgabetypen verwenden. Mit C++20 werden auch eingeschränkte Platzhalter möglich sein. Doch für den Typ des Parameters verwende ich auto. Das ist aber mit C++14 nur für verallgemeinerste Lambda-Funktionen zulässig (siehe erstes Beispiel dieses Artikels). Eine verallgemeinerte Lambda-Funktion ist unter der Decke ein Funktion-Template. Nun komme ich wieder zurück zu meinem roten Faden. getIntegral wird durch auto zum Funktions-Template. Und das ganze ohne Funktions-Template Syntax. getIntegral kann beliebige Typen annehmen und gibt nur Werte von Typen zurück, die dem Concept Integral genügen. 

Wie geht's weiter?

Ich bin natürlich absolut kein Freund der Handarbeit. Im nächsten Artikel geht es aber mit meinem roten Faden zu Platzhaltern weiter, den die Vereinheitlichung der Syntax rund um Templates, Concepts und Platzhalter geht noch weiter.

 

 

 

 

 

 

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


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare