语境
我想将一个成员函数和一个特定对象包装成一个函数对象(稍后我将用作回调)。我想为不同的成员函数和对象编写一次这个包装函数,特别是因为我的实际 lambda 在调用包装方法之前做了一些额外的工作。以下是一些可能的实现:
#include <iostream>
#include <string>
#include <utility>
template <class ClassT, class... ArgsT>
auto getCallbackPtr(ClassT* obj, void(ClassT::* memfn)(ArgsT...))
{
return [obj, memfn](ArgsT&&... args) {
(obj->*memfn)(std::forward<ArgsT>(args)...);
};
}
template <auto memFn, class ClassT>
auto getCallbackTemplate(ClassT* obj)
{
return [obj](auto&&... args){
return (obj->*memFn)(std::forward<decltype(args)>(args)...);
};
}
template <auto memFn, class ClassT, class... ArgsT>
auto getCallbackRedundant(ClassT* obj)
{
return [obj](ArgsT&&... args){
return (obj->*memFn)(std::forward<ArgsT&&>(args)...);
};
}
// Example of use
class Foo {
public:
void bar(size_t& x, const std::string& s) { x=s.size(); }
};
int main() {
Foo f;
auto c1 = getCallbackPtr(&f, &Foo::bar);
size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
auto c2 = getCallbackTemplate<&Foo::bar>(&f);
size_t x2; c2(x2, "123"); std::cout << "c2:" << x2 << "\n";
auto c3 = getCallbackRedundant<&Foo::bar, Foo, size_t&, const std::string&>(&f);
size_t x3; c3(x3, "123"); std::cout << "c3:" << x3 << "\n";
}
问题(简而言之)
我想要一个结合上述三个功能不同方面的功能:
- 它应该将成员函数作为编译时模板参数,不像
getCallbackPtr()
. - 它
operator()
不应该是模板函数,不像getCallbackTemplate()
. - 它的模板参数(成员函数指针除外)应该从函数使用中推断出来,不像
getCallbackRedundant()
.
一些细节
以下是我希望成员函数成为模板参数的原因,尽管我必须承认这些在实践中可能不会产生明显的影响:
- 优化器可能会直接调用成员函数,而不是通过函数指针。事实上,由于这是调用成员函数的唯一位置,它甚至可能被编译器内联到 lambda 中。
- 生成的函数对象更小(一个指针而不是一个指针加一个成员函数指针),因此更可能适合
std::function
(小对象优化)的足迹。
以下是getCallbackTemplate()
具有模板化的 的问题operator()
:
- 它不适用于 Visual Studio。这对我来说是一个表演终结者。(错误是
error C3533: a parameter cannot have a type that contains 'auto'
,参考template <auto memFn, class ClassT>
。) - 如果传入了错误类型的参数,我怀疑它会比非模板化的编译器错误更复杂和令人困惑
operator()
(诚然,这只是一种预感)。 - 模板
operator()
不能接受参数的初始化列表。这对我来说根本不是问题,但我提到它是为了记录。
我认为需要推断模板参数的原因相当清楚:getCallbackRedundant()
冗长且难以使用。
这可以做到吗?如何?