2016-11-22 20 views
29

C++では、関数の宣言と定義を分けることができます。 Foo.hC++でのlambdaの前方宣言

int Foo(int x); 

Foo.cppでそれを実装します。たとえば、関数を宣言することは非常に正常です。ラムダと似たようなことが可能ですか?例えば、bar.h

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

を定義し、bar.cppでそれを実装として:

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

免責事項:私はC#でラムダと経験を持っているが、私は非常にC++でそれらを使用していません。

+32

ラムダに名前を付ける場合は、通常の機能にするだけでよいでしょう。 – Brian

+0

@ブライアン知っているとそれは意味をなさない。私が質問する主な理由は好奇心であり、その可能性を知りたいのです。 – MxNx

+8

FYI; 'std :: function 'はラムダを格納することができます。ラムダには 'std :: function <...>'という型はありません。むしろ独自の一意の名前のない型を持ちます。ラムダを変数に直接格納するには、その変数の型が(autoまたはテンプレートの引数型の減算によって)推測されていればよい。 'std :: function'にはテンプレート引数型の控除の原理を使ってラムダ引数を取ることができるテンプレートコンストラクタがあります。 – Mankarse

答えて

36

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の宣言と定義を別々に宣言して定義したものです。

+2

コードサンプルは非常に説明的なものです。 – MxNx

+1

@Mzhrラムダの型はstd :: functionではなく、ラムダに固有の名前のない型です。 std :: functionは、呼び出し可能なオブジェクトの周りのポリモーフィックなラッパーです。 –

+0

@GuillaumeRacicotありがとうございます。 – MxNx

8

厳密値が(まで C++ 17)、その結果、オブジェクトCのである(++ prvalue発現はcpp reference

ラムダ式から引用

あなたされることができない話しますブロック型と呼ばれるユニークな名前の付いた非ユニオン非集約クラス型の名前付きの一時オブジェクト (これは、ブロックのスコープ、クラススコープ、または名前空間のスコープの中で最小の )で宣言されていますラムダ expres

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

+0

詳細な対応をありがとうございます。あなたの見積りの出典を聞かせてもらえますか?それはC++仕様からですか? – MxNx

6

C++のlambdasは関数ではなくオブジェクトなので、前方宣言は正しい用語ではありません。コード:

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; 
} 

(ただし実行時には狂った動作をします)。

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

コールが正常にコンパイルされ、出力として42

を生成するプログラムになります右の前に:あなたは互換性のあるstd::function変数にラムダを割り当て、例えば追加することができた

C++でラムダを驚かせることができるいくつかの非明白な事柄(あなたがこのコンセプトを持つ他の言語を知っているならば)は次の通りです:

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

  • ラムダは値で地元の人々をキャプチャすることができます(ただし、ラムダmutableを宣言しない限り、彼らはconstだ)または参照による(ただし、参照されたオブジェクトの寿命を保証することはラムダの寿命が最大であるよりも短くなりませんプログラマー)。 C++にはガベージコレクタがなく、これは "上向きFunarg"問題を正しく解決するために必要なものです(スマートポインタを取り込むことで回避できますが、リークを避けるために参照ループに注意する必要があります)。

  • 他の言語とは異なり、lambdaをコピーすることができます。コピーすると、内部で取り込まれたバイナリ変数のスナップショットが作成されます。これは可変状態のために非常に驚くかもしれません、そして、私は値によって値が取り込まれた理由がデフォルトでconstだと思います。

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

    1つの微妙な違いにもラムダキャプチャ参照と

    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); 
    } 
    

    のような基本的には次のとおりです。

合理化とラムダに関する詳細の多くを覚えておく方法は、それがコードのようなものです(通常はメンバーとしての参照を含むクラスはコピーできません)。