web-dev-qa-db-ja.com

C ++でのラムダの前方宣言

C++では、関数の宣言と定義を分離することができます。たとえば、関数を宣言するのはごく普通のことです。

int Foo(int x);

Foo.hで実装し、Foo.cppで実装します。ラムダで同じようなことをすることは可能ですか?たとえば、

std::function<int(int)> bar;

bar.hで、bar.cppで次のように実装します。

std::function<int(int)> bar = [](int n)
{
    if (n >= 5) 
        return n;
    return n*(n + 1);
};

免責事項:C#でラムダを使用した経験がありますが、C++ではあまり使用していません。

34
MxNx

lambdas の宣言と定義を分離することはできません。また、前方宣言もできません。その型は、ラムダ式で宣言される一意の名前のないクロージャー型です。しかし、ラムダを含む呼び出し可能なターゲットを格納できるように設計された std :: function オブジェクトを使用してそれを行うことができます。

サンプルコードで示したように、_std::function_を使用しています。この場合、barは実際にはグローバル変数であり、ヘッダーファイルでexternを使用して、それは宣言です(定義ではありません)。

_// bar.h
extern std::function<int(int)> bar;     // declaration
_

そして

_// bar.cpp
std::function<int(int)> bar = [](int n) // definition
{
    if (n >= 5) return n;
    return n*(n + 1);
};
_

これはラムダの個別の宣言と定義ではないことに注意してください。これは、ラムダ式から初期化されるタイプstd::function<int(int)>のグローバル変数barの宣言と定義を分離しただけです。

41
songyuanyao

C++のラムダは関数ではなくオブジェクトであるため、前方宣言は正しくありません。コード:

_std::function<int(int)> bar;
_

変数を宣言し、割り当てを強制されることはありません(その型のデフォルト値は「関数へのポインタ」です)。呼び出しさえもコンパイルできます...例えば、コード:

_#include <functional>
#include <iostream>

int main(int argc, const char *argv[]) {
    std::function<int(int)> bar;
    std::cout << bar(21) << "\n";
    return 0;
}
_

きれいにコンパイルされます(もちろん、実行時に狂ったように動作します)。

つまり、互換性のある_std::function_変数にラムダを割り当てて、たとえば次のように追加できます。

_bar = [](int x){ return x*2; };
_

呼び出しの直前では、プログラムは正常にコンパイルされ、出力42として生成されます。

C++のラムダについて驚くことがあるいくつかの非自明なもの(この概念を持つ他の言語を知っている場合)は、次のとおりです。

  • 各ラムダ[..](...){...}には、署名が完全に同一であっても、互換性のない異なる型があります。たとえば、ラムダ型のパラメーターを宣言することはできません。これは、decltype([] ...)のようなものを使用する唯一の方法ですが、呼び出し時に他の_[]..._フォームとして関数を呼び出す方法がないためです。サイトは互換性がありません。これは_std::function_によって解決されるため、ラムダを渡したりコンテナに格納したりする必要がある場合は、_std::function_を使用する必要があります。

  • ラムダは、値(ただし、ラムダconstを宣言しない限り、mutableです)または参照(ただし、参照されるオブジェクトの存続期間が、ラムダはプログラマ次第です)。 C++にはガベージコレクターがなく、これは「上向きのfunarg」問題を正しく解決するために必要なものです(スマートポインターをキャプチャすることで回避できますが、リークを避けるために参照ループに注意を払う必要があります)。

  • 他の言語とは異なり、ラムダをコピーすることができ、それらをコピーすると、キャプチャされた内部の値による変数のスナップショットが作成されます。これは変更可能な状態の場合は非常に驚くべきことであり、これがキャプチャされた値渡しの値がデフォルトでconstになる理由だと思います。

ラムダに関する詳細の多くを合理化して覚える方法は、次のようなコードです。

_std::function<int(int)> timesK(int k) {
    return [k](int x){ return x*k; };
}
_

基本的には

_std::function<int(int)> timesK(int k) {
    struct __Lambda6502 {
        int k;
        __Lambda6502(int k) : k(k) {}
        int operator()(int x) {
            return x * k;
        }
    };
    return __Lambda6502(k);
}
_

ラムダキャプチャ参照でもコピーできるというわずかな違いがあります(通常、参照をメンバーとして含むクラスではコピーできません)。

8
6502

厳密には言えない

cpp参照 からの引用

ラムダ式はprvalue式であり、その値は(C++ 17まで)であり、その結果オブジェクトは(C++ 17以降)一意の名前のない非ユニオン非集合クラスタイプの名前のない一時オブジェクトであり、クロージャタイプと呼ばれます。ラムダ式を含む最小のブロックスコープ、クラススコープ、または名前空間スコープで(ADLの目的で)宣言されている

したがって、ラムダは名前のない一時オブジェクトです。ラムダをl値オブジェクトにバインドできます(例:std::function)と変数宣言に関する通常のルールにより、宣言と定義を分離できます。

8
bashrc