考虑以下示例:
template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
callback(42);
}
int main()
{
call_me_back([](const int & value)
{
std :: cout << value << std :: endl;
});
}
在这里,我向call_me_back
提供了一个接受int
的lambda。经过一段长时间的计算后,call_me_back
会调用callback
,并callback
将其打印出来。像蛋糕一样简单。
现在,根据执行情况,call_me_back
需要使用callback
或其他类型调用int
。我们可以将前面的例子编辑为
template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
if(rand() % 2)
callback(42);
else
callback("Kill all the humans.");
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}
到目前为止一切顺利。现在callback
可以执行各种操作并根据其类型处理value
。
现在,回到第一个例子。假设call_me_back
尚未准备好立即使用callback
拨打int
。但它可以做的是存储某处的callback
,然后再调用它。
例如:
std :: function <void(const int &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = callback;
}
void ready_to_answer()
{
the_callback(42);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}
现在,callback
不是立即调用call_me_back
,而是将callback
存储在std :: function <void(const int &)>
对象中(我知道,它在全球范围内,请耐心等待)。然后很多事情都会发生,在某些时候有人可以调用ready_to_answer
,它会检索先前存储的回调并调用它。例如,ready_to_answer
可以被另一个线程调用,但是有很多原因可以解释为什么需要这样的范例,其中回调可以存储并在以后调用。
如果我想实现第二个示例,但延迟回调会怎样?。我似乎无法理解这一点。
我可以想象std :: function
是通过接受某种特定类型的虚拟调用运算符实现的。然后std :: function
将指针/引用包装到模板包装类中,该类存储实际的lambda并通过将其参数转发到它存储的lambda来实现调用操作符。好,易于。 但我不能拥有模板虚拟方法!
我尝试过各种各样的解决方案,但我找不到任何合理的解决方案。这真的不可能吗?是否不可能有一些外部提供的lambda接受存储在某处的const auto &
参数,然后再调用它?
答案 0 :(得分:7)
你是对的,对于一组无限的类型是不可能的,但如果事先知道所有类型,你就可以做到:
std :: function <void(const std :: variant<int, std :: string> &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = [callback](const auto & arg)
{
std :: visit(callback, arg);
};
}
template <typename T> void ready_to_answer(const T & x)
{
the_callback(x);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
if (std :: rand() % 2)
{
ready_to_answer(42);
}
else
{
ready_to_answer("Hi!");
}
}
答案 1 :(得分:1)
在第二个示例中,如果没有传递函数,则使用两个方法operator()(int)
和operator(char const (&)[..])
传递对匿名lambda对象的引用。因此,要存储这种回调,您需要复制lambda对象或存储特定的回调方法,即使用多个::std::function
和相应的签名。实际上,为这两种情况明确传递两个回调会更清楚。
std::function< void (int) > delalyed_int_cb;
std::function< void (const char *) > delalyed_str_cb;
template< typename callable_taking_int, typename callable_taking_string > void
call_me_back(callable_taking_int && int_cb, callable_taking_string && str_cb)
{
delalyed_int_cb = std::forward< callable_taking_int >(int_cb);
delalyed_str_cb = std::forward< callable_taking_str >(str_cb);
...
}
void
ready_to_answer()
{
if(rand() % 2)
{
delalyed_int_cb(42);
}
else
{
delalyed_str_cb("Kill all the humans.");
}
}
答案 2 :(得分:1)
您可以实现一个为您存储lambda的类模板。
#include<iostream>
template<typename L>
struct Telephone
{
L l;
Telephone(L l) : l{std::move(l)} {}
template<typename... Args>
decltype(auto) ready_to_answer(Args&&... args)
{
return l(std::forward<Args>(args)...);
}
};
template<typename L>
auto call_me_back(L&& l)
{
return Telephone<std::decay_t<L>>(std::forward<L>(l));
}
int main()
{
auto telephone = call_me_back([](auto x){ std::cout << x << std::endl; });
telephone.ready_to_answer(42);
telephone.ready_to_answer("Kill all the humans.");
}
这有一个缺点,Telephone
在lambda上是模板化的,并且对于每个lambda都是不同的类型。
如果您事先知道函数签名的外观,那么您可以从公共基类继承Telephone
并使用虚拟非模板方法。
对Telephone
的某些自动构建和管理进行了一些操作,您基本上实现了另一个std::function
,但使用了自定义签名
struct Phone
{
// all Telephone<L> inherits from Phone and have these methods
virtual void ready_to_answer(int) = 0;
virtual void ready_to_answer(const char*) = 0;
};
struct Spouse
{
std::unique_ptr<Phone> phone;
template<typename L>
Spouse(L&& l) : phone{ new Telephone<std::decay_t<L>>{std::forward<L>(l)} } {}
void ready_to_answer(int i) { phone->ready_to_answer(i); }
void ready_to_answer(const char* str) { phone->ready_to_answer(str); }
};
编辑:我继续实施了std::function
的增强版本,该版本接受任意数量的签名,因此您可以编写
function<void (int), void (std::string)> callback =
[](auto x){std::cout << x << std::endl;};
callback(42);
callback("Kill all humans!");
可以说实施并非易事,而且这里有太多的解释。