Reputation: 2316
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
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
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