我想为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是唯一类型时,此解决方案正常工作。所以传递一个lambda是可以的,但传递一个函数会默默地导致错误。然后我将该fn参数包装到lambda中,但令人惊讶的是它没有帮助;
以下是一个完整的例子:
#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
答案 0 :(得分:4)
Lambda表达式创建唯一类型,但这并不意味着每次执行都会在代码中传递一个新的唯一类型:
int main() {
for (int i=0; i<3; ++i) {
[] { std::cout << "Hello, World\n"; }();
}
}
上面的for循环创建了三个完全相同类型的对象。
在您的代码中,您有一个包含lambda表达式的模板函数iterTest
。这意味着对于iterTest
的每个实例化,lambda表达式将具有唯一类型。但是,如果运行iterTest
的同一实例化,则lambda表达式每次都具有相同的类型。这就是这里发生的事情。
作为示例,这里有一个解决方法,只需对您的示例代码进行最少的更改:
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 :(得分:1)
为每个lambda实例化模板函数会生成新的函数定义。因为lambda函数类型不同(即使它们具有相同的声明)。
所以每个定义都有自己的static firstItem
。
但是对于foo
,只有一个定义,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>()
是不同的类型,因此它们会为它们制作两个不同的fromSecondIter
。然后firstItem
保持不变。