web-dev-qa-db-ja.com

引数としてラムダを渡す-参照または値によって?

ファンクターを引数として取り、いくつかの処理の後にそれを実行するテンプレートコードを記述しました。他の誰かがその関数をラムダ、関数ポインタ、またはstd::functionしかし、それは主にラムダのためのものです(私が他の形式を禁止するわけではありません)。私はそのラムダをどのように取るべきかを質問したい-値によって?参照によって?または、他の何か。

コード例-

#include <iostream>
#include <functional>
using namespace std;

template<typename Functor>
void f(Functor functor)
{
    functor();
}

void g()
{
    cout << "Calling from Function\n";
}

int main() 
{
    int n = 5;

    f([](){cout << "Calling from Temp Lambda\n";});

    f([&](){cout << "Calling from Capturing Temp Lambda\n"; ++n;});

    auto l = [](){cout << "Calling from stored Lambda\n";};
    f(l);

    std::function<void()> funcSTD = []() { cout << "Calling from std::Function\n"; };
    f(funcSTD);

    f(g);
}

上記のコードでは、これらのどちらにするかを選択できます-

template<typename Functor>
    void f(Functor functor)

template<typename Functor>
    void f(Functor &functor)

template<typename Functor>
    void f(Functor &&functor)

より良い方法は何ですか?なぜですか?これらに制限はありますか?

21
hg_git

考えられる欠点として、ラムダがコピー可能でない場合、コピーによる受け渡しが機能しないことに注意してください。あなたがそれでうまくいくことができれば、コピーで渡すことは問題ありません。
例として:

#include<memory>
#include<utility>

template<typename F>
void g(F &&f) {
    std::forward<F>(f)();
}

template<typename F>
void h(F f) {
    f();
}

int main() {
    auto lambda = [foo=std::make_unique<int>()](){};

    g(lambda);
    //h(lambda);
}

上記のスニペットでは、lambdaのため、fooはコピーできません。 std::unique_ptrのコピーコンストラクターが削除されるという事実の結果として、そのコピーコンストラクターが削除されます。
反対側では、F &&fはlvalueとrvalueの両方の参照を転送参照として受け入れ、const参照も受け入れます。
言い換えると、再利用引数として同じラムダを複数回使用する場合、関数がオブジェクトをコピーで取得し、コピーできないため移動する必要がある場合はできません。 (まあ、実際にはそうすることができます、それは参照によって外側のものをキャプチャするラムダでそれをラップすることの問題です)。

8
skypjack

ラムダ式は独自のフィールド(クラスなど)を持つことができるため、参照をコピー/使用すると異なる結果が生じる可能性があります。簡単な例を次に示します。

template<class func_t>
size_t call_copy(func_t func) {
    return func(1);
}

template<class func_t>
size_t call_ref(func_t& func) {
    return func(1);
}

int main() {
    auto lambda = [a = size_t{0u}] (int val) mutable {
        return (a += val);
    };
    lambda(5);

    // call_ref(lambda); – uncomment to change result from 5 to 6
    call_copy(lambda);

    std::cout << lambda(0) << std::endl;

    return 0;
}

ところで、パフォーマンスで判断したい場合、実際には違いはありません。ラムダは非常に小さく、コピーが簡単です。

また、ラムダをパラメーターとして(それを含む変数ではなく)渡す場合は、転送参照を使用して機能するようにする必要があります。

4
Kamil Koczurek