2

次のパターンのコードがたくさんあるため、2 回目の反復からのみ実行される for-cycle のヘルパー関数を作成したいと考えています。

firstItem = true;
for (unsigned i = 0; i < 5; ++i)
{
  firstItem ? firstItem = false : std::cout << ",\n";
  std::cout << i;
}

このためのヘルパー関数を考え始め、次の解決策を思いつきました。

template <typename T>
void fromSecondIter(T fn)
{
  static bool firstItem = true; // because of this it works with unique types only
  if (firstItem)
    firstItem = false;
  else
    fn();
}

私の問題は、 T が一意の型の場合、このソリューションが正しく機能することです。したがって、ラムダを渡すことは問題ありませんが、関数を渡すと暗黙のうちにエラーが発生します。次に、その fn パラメーターをラムダにラップしましたが、驚くべきことに、それは役に立ちません。

完全な例を次に示します。

#include <type_traits>
#include <iostream>
#include <functional>

template <typename T>
void fromSecondIter(T fn)
{
  static bool firstItem = true;
  if (firstItem)
    firstItem = false;
  else
    fn();
}

template <typename T>
void iterTest(T fn)
{
  std::cout << "Numbers: ";
  for (unsigned i = 0; i < 5; ++i)
  {
    fromSecondIter([&fn](){ fn(); }); // bad, why lambda is not unique here???
    std::cout << i;
  }
  std::cout << std::endl;
}

void foo()
{
  std::cout << ", ";
}

void test()
{
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest(foo); // ok
  iterTest(foo); // bad
}

int main()
{
  test();

  return 0;
}

これは次のように表示されます:

Numbers: 0, 1, 2, 3, 4
Numbers: 0, 1, 2, 3, 4
Numbers: 0, 1, 2, 3, 4
Numbers: , 0, 1, 2, 3, 4
4

2 に答える 2

5

ラムダ式は一意の型を作成しますが、これは、実行がコード内の 1 つを通過するたびに、新しい一意の型が作成されるという意味ではありません。

int main() {
  for (int i=0; i<3; ++i) {
    [] { std::cout << "Hello, World\n"; }();
  }
}

上記の for ループは、すべて同じタイプの 3 つのオブジェクトを作成します。

iterTestコードには、ラムダ式を含むテンプレート関数 があります。iterTestこれは、ラムダ式のインスタンス化ごとに一意の型を持つことを意味します。ただし、ラムダ式の同じインスタンス化を実行すると、iterTest毎回同じ型になります。それがここで起こっていることです。

例として、サンプル コードの変更を最小限に抑えた 1 つの回避策を次に示します。

template <typename T>
void iterTest(T fn)
{
  std::cout << "Numbers: ";
  for (unsigned i = 0; i < 5; ++i)
  {
    fromSecondIter(fn);
    std::cout << i;
  }
  std::cout << std::endl;
}

void test()
{
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([]{foo();}); // ok
  iterTest([]{foo();}); // also ok
}

の現在の実装でfromSecondIterは、作成からプログラムの終了まで続く静的変数が作成され、プログラムの開始からメモリが予約されます。大きなコストではありませんが、もっと良い方法があります。代わりに bool メンバー変数を持つオブジェクトをoperator()、必要なロジックを持つメンバー関数 (例: ) と共に作成します。

#include <iostream>

struct skipper {
  bool first = true;

  template<typename Fn>
  void operator() (Fn &&fn) {
    if (first)
      first = false;
    else
      fn();
  }
};

int main()
{
  skipper skip_first;
  for (int i=0; i<10; ++i) {
    skip_first([]{ std::cout << ", "; });
    std::cout << i;      
  }
}

これは、1 行ではなく 2 行で使用できることを意味しますが、スコープを制御できるようになりました。

于 2013-10-10T16:14:52.613 に答える
1

各ラムダのテンプレート関数をインスタンス化すると、新しい関数定義が作成されます。ラムダ関数の型が異なるため (宣言が同じであっても)。

したがって、すべての定義には独自の がありstatic firstItemます。

しかし、foo定義が 1 つしかないためstatic、最初に を呼び出した後に が変更されますfoo

iterTest([](){ std::cout << ", "; }); // iterTest, fromSecondIter for this lambda
iterTest([](){ std::cout << ", "; }); // iterTest, fromSecondIter for this lambda
iterTest(foo);                        // iterTest, fromSecondIter for this foo
iterTest(foo);                        // uses above functions

問題を示す単純な例は次のとおりです。

template <int i>
struct foo
{
    void operator()()
    {
        std::cout << ", ";
    }
};


void test()
{
  iterTest([](){ std::cout << ", "; });
  iterTest([](){ std::cout << ", "; });
  iterTest(foo<1>());
  iterTest(foo<2>());
}

foo<1>()foo<2>()は異なるタイプなので、2 つの異なるものfromSecondIterになります。その後firstItemは変わらず。

于 2013-10-10T15:57:56.927 に答える