彼の本The C++ Standard Library (Second Edition)
Nicolai Josuttisは、ラムダは単純な関数よりもコンパイラによって最適化できると述べています。
さらに、C++コンパイラは、通常の関数よりもラムダを最適化します。 (213ページ)
何故ですか?
インライン化に関しては、これ以上の違いはないはずです。私が考えることができた唯一の理由は、コンパイラがラムダでより良いローカルコンテキストを持っているかもしれないということです。
ラムダは関数オブジェクトであるため、それらを関数テンプレートに渡すと、そのオブジェクト専用の新しい関数がインスタンス化されます。したがって、コンパイラはラムダ呼び出しを簡単にインライン化できます。
一方、関数の場合、古い警告が適用されます。関数pointerが関数テンプレートに渡され、コンパイラーは従来、関数ポインターを介した呼び出しのインライン化に多くの問題を抱えています。 canは、理論的にはインライン化されますが、周囲の関数もインライン化されている場合のみです。
例として、次の関数テンプレートを検討してください。
_template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
for (; begin != end; ++begin)
*begin = f(*begin);
}
_
次のようなラムダで呼び出します:
_int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });
_
このインスタンス化の結果(コンパイラーにより作成):
_template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
for (; begin != end; ++begin)
*begin = f.operator()(*begin);
}
_
…コンパイラは_some_lambda_type::operator ()
を認識しており、それを簡単にインライン呼び出しできます。 (そして、関数map
をany他のラムダで呼び出すと、各ラムダには異なるタイプがあるため、map
の新しいインスタンスが作成されます。)
ただし、関数ポインターで呼び出されると、インスタンス化は次のようになります。
_template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
for (; begin != end; ++begin)
*begin = f(*begin);
}
_
…そしてここでf
はmap
への呼び出しごとに異なるアドレスを指しているため、f
への呼び出しもインライン化されていない限り、コンパイラはmap
への呼び出しをインライン化できません。コンパイラがf
を特定の1つの関数に解決できること。
アルゴリズムに「関数」を渡すと、実際には関数へのポインターを渡すため、関数へのポインターを介して間接呼び出しを行う必要があるためです。ラムダを使用する場合、その型用に特別にインスタンス化されたテンプレートインスタンスにオブジェクトを渡し、ラムダ関数の呼び出しは直接呼び出しであり、関数ポインターを介した呼び出しではないため、インライン化される可能性が高くなります。