Warum können lambdas vom Compiler besser optimiert werden als einfache functionen?

In seinem Buch The C++ Standard Library (Second Edition) Nicolai Josuttis fest, dass Lambdas vom Compiler besser optimiert werden können als einfache functionen.

Außerdem optimieren C ++ – Compiler Lambdas besser als normale functionen. (Seite 213)

Warum das?

Ich dachte, wenn es um Inlining geht, sollte es keinen Unterschied mehr geben. Der einzige Grund, an den ich denken könnte, ist, dass Compiler mit lambdas einen besseren lokalen Kontext haben und so mehr Annahmen treffen und mehr Optimierungen vornehmen können.

Der Grund dafür ist, dass Lambdas functionsobjekte sind. Wenn Sie sie also an eine functionsvorlage übergeben, wird eine neue function speziell für dieses Objekt instanziiert. Der Compiler kann somit den Lambda-Aufruf trivial einfügen.

Für functionen dagegen gilt der alte Caveat: Ein functionszeiger wird an die functionsschablone weitergegeben, und Compiler haben traditionell viele Probleme, Aufrufe über functionszeiger anzuordnen. Sie können theoretisch inline sein, aber nur, wenn die umgebende function ebenfalls inline ist.

Betrachten Sie als Beispiel die folgende functionsvorlage:

 template  void map(Iter begin, Iter end, F f) { for (; begin != end; ++begin) *begin = f(*begin); } 

Nenne es mit einem Lambda wie folgt:

 int a[] = { 1, 2, 3, 4 }; map(begin(a), end(a), [](int n) { return n * 2; }); 

Ergebnisse in dieser Instanziierung (vom Compiler erstellt):

 template <> void map(int* begin, int* end, _some_lambda_type f) { for (; begin != end; ++begin) *begin = f.operator()(*begin); } 

… der Compiler kennt _some_lambda_type::operator () und kann diese inline _some_lambda_type::operator () . (Und das Aufrufen der functionszuordnung mit einem anderen Lambda würde eine neue Instanz der map erstellen, da jedes Lambda einen unterschiedlichen Typ hat.)

Wenn sie jedoch mit einem functionszeiger aufgerufen wird, sieht die Instanziierung wie folgt aus:

 template <> void map(int* begin, int* end, int (*f)(int)) { for (; begin != end; ++begin) *begin = f(*begin); } 

… und hier zeigt f auf eine andere Adresse für jeden zu mappenden Aufruf, und daher kann der Compiler keine Inline-Aufrufe an f ausführen, es sei denn, der umgebende Call to map wurde ebenfalls inline, so dass der Compiler f zu einer bestimmten function auflösen kann.

Denn wenn Sie eine “function” an einen Algorithmus übergeben, übergeben Sie tatsächlich einen pointers an die function, so dass er einen indirekten Aufruf über den pointers auf die function ausführen muss. Wenn Sie ein Lambda verwenden, übergeben Sie ein Objekt an eine Vorlageninstanz, die speziell für diesen Typ instanziiert wurde, und der Aufruf der Lambda-function ist ein direkter Aufruf, kein Aufruf über einen functionszeiger, so dass es viel wahrscheinlicher inline sein kann.