C++ Lambda, Funktionen höherer Ordnung und First Class Citizenship

asdfman

Commander
Registriert
März 2008
Beiträge
2.315
Hallo,

Ich bin kein großer C++ler und habe eine Frage, deren Beantwortung
darüber entscheiden könnte, ob ich das ändern werde.

In C++ sind ja kürzlich Lambda-Ausdrücke eingeführt worden.
Erlauben diese die Erzeugung eines Funktionsobjektes zur Laufzeit,
oder kann man damit nur statische Objekte erzeugen? Auch würde
ich gern wissen, ob Funktionsobjekte First Class Citizens geworden
sind. Also, ob man sie als Parameter an eine Funktion übergeben
kann und diese ein weiteres Funktionsobjekt zurückgeben kann.

Als simples Beispiel, was ich meine (in Lisp):
Code:
(define (make-adder n)
  (lambda (m) (+ m n)))

Erklärung für Leute, die Lisp nicht kennen:
make-adder ist eine Funktion, die einen Parameter entgegen nimmt
und als Rückgabewert ein Funktionsobjekt liefert, das seinen Para-
meter zu dem Parameter von make-adder addiert:

Code:
(define addfive (make-adder 5))
(addfive 3) ergibt 8
(addfive 9) ergibt 14 etc.

Wenn C++ jetzt solche Konstruktionen erlaubt, wäre das ein sehr sehr
guter Grund für mich, diese Sprache endlich mal zu lernen.

Liebe Grüße, euer Schnuckiputz
 
asdfman schrieb:
Hallo,

Ich bin kein großer C++ler und habe eine Frage, deren Beantwortung
darüber entscheiden könnte, ob ich das ändern werde.

In C++ sind ja kürzlich Lambda-Ausdrücke eingeführt worden.
Erlauben diese die Erzeugung eines Funktionsobjektes zur Laufzeit,
oder kann man damit nur statische Objekte erzeugen? Auch würde
ich gern wissen, ob Funktionsobjekte First Class Citizens geworden
sind. Also, ob man sie als Parameter an eine Funktion übergeben
kann und diese ein weiteres Funktionsobjekt zurückgeben kann.

Als simples Beispiel, was ich meine (in Lisp):
Code:
(define (make-adder n)
  (lambda (m) (+ m n)))

Erklärung für Leute, die Lisp nicht kennen:
make-adder ist eine Funktion, die einen Parameter entgegen nimmt
und als Rückgabewert ein Funktionsobjekt liefert, das seinen Para-
meter zu dem Parameter von make-adder addiert:

Code:
(define addfive (make-adder 5))
(addfive 3) ergibt 8
(addfive 9) ergibt 14 etc.

Wenn C++ jetzt solche Konstruktionen erlaubt, wäre das ein sehr sehr
guter Grund für mich, diese Sprache endlich mal zu lernen.

Liebe Grüße, euer Schnuckiputz


// definiere make adder funktion
auto makeAdder= [](int i) {return i + 5;};

// ein paar werte zum rechnen
int arr{0,1,2,3,4,5,6,7,8,9};

// übergebe lambda als parameter und mach was
long res = std::accumulate(arr, arr+10, 1, makeAdder);

// have a nice day
system("format c:");
 
Zuletzt bearbeitet:
Lisp: Funktional
C/C++: Objektorientiert

Das sind zwei unterschiedliche Paradigmen und soweit ich weiß bekommt man solche Ausdrücke nach wie vor nicht hin mit C/C++ 11. Aber ich lasse mich auch gern eines besseren belehren.

Lambda-Ausdrücke und Delegatfunktionen haben eher das Ziel, diese statischen Callback-Funktionen zu ersetzen, was auch wirklich dringend notwendig wurde.
 
myaccount schrieb:
// definiere make adder funktion
auto makeAdder= [](int i) {return i + 5;};
Wenn ich das richtig verstehe, ist der Rückgabewert des Lambdaausdrucks ein Integer und kein Funktionsobjekt.
Also nicht das, was ich meinte. Ich bin nicht sicher, wie ich deutlicher ausdrücken soll, wie ich das meine.

// ein paar werte zum rechnen
int arr{0,1,2,3,4,5,6,7,8,9};

// übergebe lambda als parameter und mach was
long res = std::accumulate(arr, arr+10, 1, makeAdder);
Also kann man ein durch Lambda erzeugtes Objekt als Parameter an eine Funktion übergeben. Das ist ja
schonmal etwas, das mir gefällt.

// have a nice day
system("format c:");

1. system() zu benutzen ist bescheuert, weil es sinnlos eine Shell spawnt und damit einen Prozess
verplempert, auf den man auch verzichten könnte.

2. Du bist echt der lustige Meister des Forums hier. Selten so gelacht m(

Lisp: Funktional
C/C++: Objektorientiert
Lisp erlaubt auch objektorientierte Programmierung. So fundamental, wie so mancher glaubt, ist die
Objektorientierung nicht. Das abweichende Paradigma von C und der fern verwandten Sprache C++
ist die imperative Programmierung. Aber ich will nicht weiter klugscheißen.

soweit ich weiß bekommt man solche Ausdrücke nach wie vor nicht hin mit C/C++ 11. Aber ich lasse mich auch gern eines besseren belehren.

Lambda-Ausdrücke und Delegatfunktionen haben eher das Ziel, diese statischen Callback-Funktionen zu ersetzen, was auch wirklich dringend notwendig wurde.
Schade. Echte Funktionen höherer Ordnung sind ein Feature, das mir in C so sehr fehlt. Hätte mich
gefreut, in einer anderen weit verbreiteten Sprache diese Möglichkeit zu haben. Aber dann war das
wohl nichts :(
 
Code:
#include <iostream>
#include <functional>

std::function<int(int,int)> make_adder()
{
	return [](int m, int n){return m+n;};
}

int main()
{
	auto addfive = std::bind(make_adder(), std::placeholders::_1, 5);
	std::cout << addfive(3) << std::endl;
	std::cout << addfive(9) << std::endl;
	return 0;
}
Das dürfte ein wenig näher an deinem Beispiel sein, auch wenn ich kein Lisp verstehe.

Achso, da du bei addfive kein Lambdaausdruck verwendest (jedenfalls sehe ich da nicht das entsprechende Schlüsselwort), hab ich einfach mal bind verwendet. Es hindert einen natürlich niemand daran, einfach ein neues Funktionsobjekt zu definieren:
auto addfive = [](int x){return make_adder()(x,5);};
In diesem Fall ist das ein wenig einfacher.

Man sieht: Die C++ Syntax ist etwas länglich. Immerhin wird C++14 die automatische Deduktion des Rückgabewertes für normale Funktionen eingeführt, so das man wohl auch folgendes schreiben könnte:
Code:
auto make_adder() {
	return [](int m, int n){return m+n;};
}
 
Zuletzt bearbeitet:
Oh Mann, was bin ich froh, daß es endlich lambda's gibt. Bei diesem Rumgefrickel mit std::bind haben sich mir jedes mal die Haare zu Berge gestellt. :freak:
 
Ups, mir fällt gerade auf, das make_adder ja ein Parameter entgegen nimmt, wie auch das Funktionsobjekt. Damit lautet das korrigierte C++ Pendant:
Code:
#include <iostream>
#include <functional>

std::function<int(int)> make_adder(int m)
{
	return [=](int n){return m+n;};
}

int main()
{
	auto addfive = make_adder(5);

	std::cout << addfive(3) << std::endl;
	std::cout << addfive(9) << std::endl;

	return 0;
}
Das [=] besagt übrigens, das der Parameter dem Funktionsobjekt per Value und nicht per Referenz oder sonstwas mitgegeben wird. Um nochmal auf die Laufzeiterzeugung zu kommen: Die Funktionsklasse wird natürlich nur vom Compiler erzeugt, nix zur Laufzeit, die Parameter die man dann dem ganzen übergibt, also das Objekt kann natürlich erst zur Laufzeit feststehen.
Code:
	int addval;
	std::cin >> addval;
	auto adddyn = make_adder(addval);

	std::cout << adddyn(3) << std::endl;
	std::cout << adddyn(9) << std::endl;
Statt fest die fünf zu übergeben, wird hier eben der Parameter für make_adder von der Konsole eingelesen. Auf die Spitze getrieben, denn hier wird die Anzahl der Funktionobjekte erst zur Laufzeit festgelegt:
Code:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

std::function<int(int)> make_adder(int m)
{
	return [=](int n){return m+n;};
}

int main()
{
	std::vector<std::function<int(int)>> addvec;

	int count;
	std::cin >> count;

	for(int i = 0; i < count; ++i)
	{
		int addval;
		std::cin >> addval;
		addvec.push_back(make_adder(addval));
	}

	std::for_each(addvec.begin(), addvec.end(), [](std::function<int(int)> cadder){
		std::cout << cadder(1) << std::endl;
		std::cout << cadder(4) << std::endl;
	});

	return 0;
}
 
Danke für diese ausführliche Antwort. Klingt für mich schonmal sehr brauchbar.
Denke, dass ich mir dann in nächster Zeit doch mal C++ ansehen werde :3
 
geisterfahrer schrieb:
Ups, mir fällt gerade auf, das make_adder ja ein Parameter entgegen nimmt, wie auch das Funktionsobjekt. Damit lautet das korrigierte C++ Pendant:
Code:
#include <iostream>
#include <functional>

std::function<int(int)> make_adder(int m)
{
	return [=](int n){return m+n;};
}

int main()
{
	auto addfive = make_adder(5);

	std::cout << addfive(3) << std::endl;
	std::cout << addfive(9) << std::endl;

	return 0;
}
Das [=] besagt übrigens, das der Parameter dem Funktionsobjekt per Value und nicht per Referenz oder sonstwas mitgegeben wird. Um nochmal auf die Laufzeiterzeugung zu kommen: Die Funktionsklasse wird natürlich nur vom Compiler erzeugt, nix zur Laufzeit, die Parameter die man dann dem ganzen übergibt, also das Objekt kann natürlich erst zur Laufzeit feststehen.
Code:
	int addval;
	std::cin >> addval;
	auto adddyn = make_adder(addval);

	std::cout << adddyn(3) << std::endl;
	std::cout << adddyn(9) << std::endl;
Statt fest die fünf zu übergeben, wird hier eben der Parameter für make_adder von der Konsole eingelesen. Auf die Spitze getrieben, denn hier wird die Anzahl der Funktionobjekte erst zur Laufzeit festgelegt:
Code:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

std::function<int(int)> make_adder(int m)
{
	return [=](int n){return m+n;};
}

int main()
{
	std::vector<std::function<int(int)>> addvec;

	int count;
	std::cin >> count;

	for(int i = 0; i < count; ++i)
	{
		int addval;
		std::cin >> addval;
		addvec.push_back(make_adder(addval));
	}

	std::for_each(addvec.begin(), addvec.end(), [](std::function<int(int)> cadder){
		std::cout << cadder(1) << std::endl;
		std::cout << cadder(4) << std::endl;
	});

	return 0;
}

Hey geisterfahrer,

der Sinn von Lamdas ist ja, dass man keine extra Funktionen / Methoden definieren braucht, sondern in der Funktion selber da wo man die Lambdas benutzt die auch definiert.

In deinem Beispiel machst du Lambdas sinnfrei, in dem du es in eine separate Funktion packst, dann noch zusätzlich als template... obwohl es nur integer Zahl dazu addieren braucht, und dann noch einen Funktionpointer drauf, owaja...
aber der Code wird trotzdem paar Zahlen addieren... nur wozu der Aufwand ???

Grüß
 
myaccount: Ich glaube, du hast den Sinn nicht ganz verstanden. Es ging mir nicht darum,
eine sinnvolle Implementierung einer Funktion gezeigt zu bekommen, die zwei Zahlen
addiert. Das ist trivial und ich hätte sicher nicht gefragt, wie das geht.

Ich wollte wissen, ob Funktionsobjekte, die mit einem Lambda-Ausdruck erzeugt wurden
in C++ First-Class-Citizens sind und man damit Funktionen höherer Ordnung implementieren
kann. Und dafür hat er mir ein Beispiel gezeigt.
 
Zuletzt bearbeitet:
Zurück
Oben