假设我们有班级Obj
和主要版本:
class Obj
{
public:
void func1(int n) {}
void func2(std:string n) {}
};
std::vector<Obj> retrieveObjs()
{
std::vector<Obj> result;
// ...
return result;
}
int main()
{
// Call func1 for all obj
{
auto objs = retrieveObjs();
for (auto& obj : objs)
{
obj.func1(100);
}
}
// Call func2 for all obj
{
auto objs = retrieveObjs();
for (auto& obj : objs)
{
obj.func2("xxx");
}
}
return 0;
}
我希望有一个泛型函数来调用所有objs中的特定函数,如下面的伪代码。
void invokeAll(FUNCTION f, PARAM p) // pseudocode
{
auto objs = retrieveObjs();
for (auto& obj : objs)
{
obj.f(p);
}
}
int main() // pseudocode
{
invokeAll(func1, 100);
invokeAll(func2, "xxx");
}
我不知道如何替换FUNCTION
和PARAM
以使其发挥作用。
使用template / lambda / for_each或类似的技巧可以做到吗?
答案 0 :(得分:4)
您所描述的是指向成员函数的指针的一个很好的用例,其语法如下所示:
// get the pointer to the function.
auto funPtr = &Obj::func1;
Obj obj;
// call the method using the function pointer
obj.(*funPtr)();
在您的情况下,您可以将函数指针作为参数接收,将参数作为包接收。
// F is the type of the function pointer.
// As arguments and return type of `f` can change, so it's type `F` can.
template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
for (auto&& obj : retrieveObjs()) {
// We call member `f` with `obj`
// We expand the pack `args` to send it as multiple arguments
obj.(*f)(args...);
}
}
您将能够以类似的方式调用该函数:
// Notice the member function pointer syntax
invokeAll(&Obj::func1, 100);
// Work with multiple arguments, [100, "test"] will be packed into `args`
invokeAll(&Obj::func2, 100, "test");
在C ++ 17中,使用std::invoke
,您可以通过允许以Obj
作为参数的任何类型的函数来进一步概括您的情况:
template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
for (auto&& obj : retrieveObjs()) {
// invoke function `f` with `obj` as it's object and `args` as parameter.
std::invoke(f, obj, args...);
}
}
如果你现在想要支持更多类型的功能,包括lambdas,你可以使用void_t
style sfinae:
// The compiler will pick this function if `obj.(*f)(args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(std::declval<Obj>().(*f)(args...))> {
// Here's the constraint ------^
for (auto&& obj : retrieveObjs()) {
obj.(*f)(args...);
}
}
// The compiler will pick this function if `f(obj, args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(f(std::declval<Obj>(), args...))> {
// Here's the constraint ------^
for (auto&& obj : retrieveObjs()) {
f(obj, args...);
}
}
void_t
定义如下:
template<typename...>
using void_t = void;
然后,通过它,你也可以解锁这个语法:
invokeAll([](Obj& obj, int a){
// this block will be called for each `obj` in `retrieveObjs`
}, 100);
如果您也想支持不可复制的类型,请查找完美转发。
答案 1 :(得分:1)
template<class F, class R>
void invoke_on_range( F&& f, R&& r ) {
std::for_each( r.begin(), r.end(), std::forward<F>(f) );
}
这需要一个范围并在范围的每个元素上调用lambda。
int main() {
invoke_on_range( [](Obj& obj){ obj.func1(100); }, retrieveObjs() );
invoke_on_range( [](Obj& obj){ obj.func2("xxx"); }, retrieveObjs() );
}
有一些样板来编写lambda,但结构不是你的问题。
我发现这有时也很有用:
template<class F, class...Args>
void invoke_on_each( F&& f, Args&&...args ) {
using discard=int[];
(void)discard{0,(void(
f( std::forward<Args>(args) )
),0)...};
}
这需要一个lambda f
和一组args...
。它在每个f
上运行一次args...
。奇怪的discard
技巧涉及制作所有0
的数组并抛弃它(优化器不会这样做),以便生成...
完全符合我们要求的上下文
隐藏您在retrieveObjs
上运行的事实似乎不值得编写另一个包装函数,但也可以这样做。
如果要将界面与实现分开,可以将class F
和F&&
替换为std::function<void(Obj&)>
,以获得适度的性能费用。