web-dev-qa-db-ja.com

クラステンプレートパラメーターとしてのラムダ式

ラムダ式をasクラステンプレートパラメーターとして使用できますか? (これは、ラムダ式自体をテンプレート化できるかどうかを尋ねる this one とは非常に異なる質問であることに注意してください。)

私はあなたが次のようなことができるかどうか尋ねています:

template <class Functor> 
struct Foo { };
// ...
Foo<decltype([]()->void { })> foo;

これは、たとえば、クラステンプレートにequal_toなどのさまざまなパラメーターがあり、通常は1行のファンクターとして実装されている場合に役立ちます。たとえば、独自のカスタム等価比較関数を使用するハッシュテーブルをインスタンス化するとします。私は次のようなことを言いたいです:

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  decltype([](const std::string& s1, const std::string& s2)->bool 
    { /* Custom implementation of equal_to */ })
  > map_type;

しかし、私はGCC 4.4および4.6でこれをテストしましたが、ラムダ式で作成された匿名型にはデフォルトのコンストラクタがないため、明らかに動作しません。 (boost::bindで同様の問題を思い出します。)ドラフト標準でこれが許可されない理由はありますか、それとも間違っているのでしょうか。

59
Channel72

私はあなたが次のようなことができるかどうか尋ねています:

Foo<decltype([]()->void { })> foo;

ラムダ式は未評価のコンテキスト(decltypesizeofなど)に表示されないため、できません。 C++ 0x FDIS、5.1.2 [expr.prim.lambda] p2

ラムダ式の評価の結果、prvalueが一時的になります(12.2)。この一時はクロージャーオブジェクトと呼ばれます。 ラムダ式は未評価のオペランドには現れません(5節)。 [注:クロージャーオブジェクトは関数オブジェクト(20.8)のように動作します。—注の終了](emphasis mine)

最初に特定のラムダを作成してから、その上でdecltypeを使用する必要があります。

auto my_comp = [](const std::string& left, const std::string& right) -> bool {
  // whatever
}

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  decltype(my_comp)
  > map_type;

これは、ラムダから派生した各クロージャーオブジェクトが完全に異なる型を持つことができるため、結局、anonymous関数のようなものだからです。

52
Xeo

@Xeoが理由を教えてくれたので、回避策を紹介します。

多くの場合、クロージャーに名前を付けたくない場合があります。この場合、std::function、これはタイプです:

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  std::function<bool(std::string const&, std::string const&)>
  > map_type;

関数のシグネチャを正確にキャプチャすることに注意してください。

次に、マップを作成するときにラムダを記述するだけです。

unordered_map、等値比較を変更する場合は、動作に合わせてハッシュを変更することをお勧めします。等しいと比較するオブジェクトは同じハッシュを持つものとします。

9
Matthieu M.

状態は型に含まれていないため、クロージャを使用してこれを行うことはできません。

ラムダがステートレス(キャプチャなし)の場合は、大丈夫です。この場合、ラムダは通常の関数ポインターに減衰します。これは、ラムダ型の代わりにテンプレート引数として使用できます。

gccはそれを好まない。 http://ideone.com/bHM3n

5
Ben Voigt

std::functionなどの実行時抽象型を使用するか、ローカル変数またはテンプレートクラスの一部として型を作成する必要があります。

0
Puppy