How to create unique type for lambdas?

I want to create a helper function for a for-cycle that only runs from the second iterations because I have a lots of code with the following pattern:

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

I started to think a helper function for this, and I came up with this solution:

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

My problem is that this solution is working correctly when T is a unique type. So passing a lambda is ok, but passing a function will silently cause error. Then I wraped that fn parameter to a lambda, but surprisingly it does not help;

Here is a full example:

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

this prints:

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

Upvotes: 2

Views: 399

Answers (2)

bames53
bames53

Reputation: 88155

Lambda expressions create unique types, but that doesn't mean that every time execution passes over one in the code that a new unique type is created:

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

The above for loop creates three objects all of the same type.

In your code you have a template function, iterTest, that contains a lambda expression. This means that for each instantiation of iterTest the lambda expression will have a unique type. However if you run the same instantiation of iterTest the lambda expression have the same type each time. That's what's going on here.

As an example here's one workaround, with minimal changes for your example code:

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
}

Your current implementation of fromSecondIter will create a static variable that lasts from its creation until the end of the program, and memory will be reserved for it from the beginning of the program. It's not a huge cost, but there's a better way; Create an object that has a bool member variable instead, along with a member function (e.g., operator()) with the necessary logic.

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

It means using it takes two lines instead of one, but now you have control over the scope.

Upvotes: 5

masoud
masoud

Reputation: 56479

Instantiating template-functions for each lambda makes a new function definition. Because lambda function types are different (even if they have same declaration).

So every definition has its own static firstItem.

But for foo there is just one definition, then the static will be changed after first call for 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

A naive example to demonstrate the issue is:

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>() and foo<2>() are different types, so it will make two different fromSecondIter for them. Then firstItem is unchanged.

Upvotes: 1

Related Questions