将 lambda 参数完美转发到成员函数,其中成员函数是非类型模板参数
Perfect forwarding of lambda arguments to member function, where member function is a non-type template parameter
上下文
我想将一个成员函数和一个特定的对象包装成一个函数对象(稍后我将使用它作为回调)。我想为不同的成员函数和对象编写一次这个包装函数,特别是因为我的实际 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()
过于冗长且难以使用。
这能做到吗?怎么样?
I would like a function that combines different aspects of the above three functions [...]
如果我理解正确,你想要什么...在我看来这是可能的,但我看到的只是一个复杂的解决方案。
希望其他人可以提出一个更简单的方法,我使用了几个助手:一个仅声明的模板函数 gth1()
从方法指针
检测 Args...
template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;
以及模板 gth2
结构的特化,该结构具有构造和 return lambda 的静态方法(Holt 的更正:谢谢!)
template <typename, typename, auto>
struct gth2;
template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
{
static auto getLambda (ClassT * obj)
{ return [obj](ArgsT ... args)
{ return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
};
现在可以写一个getCallback()
函数如下
template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
{ return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); }
以下是完整的工作示例
#include <iostream>
template <typename, typename, auto>
struct gth2;
template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
{
static auto getLambda (ClassT * obj)
{ return [obj](ArgsT ... args)
{ return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
};
template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;
template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
{ return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); }
// Example of use
struct Foo
{ void bar(size_t& x, const std::string& s) { x=s.size(); } };
int main ()
{
Foo f;
auto l { getCallback<&Foo::bar>(&f) };
size_t x;
l(x, "1234567");
std::cout << x << "\n";
}
推导参数的一种简单方法是使用部分模板特化。
在这个例子中,我通过转发非类型成员函数指针 和 来解决问题,它是自定义仿函数的类型,然后返回。
部分专注于类型,剩下的就很简单了。
#include <iostream>
#include <string>
template <auto memFnPtr, class memFn>
struct getCallbackTemplate;
template <auto memFnPtr, class Ret, class ClassT, class... Args>
struct getCallbackTemplate<memFnPtr, Ret(ClassT::*)(Args...)>
{
getCallbackTemplate (ClassT* obj) : m_obj(obj) {}
Ret operator()(Args... args) {
return (m_obj->*memFnPtr)(std::forward<Args>(args)...);
}
ClassT* m_obj;
};
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj) {
return getCallbackTemplate<memFn, decltype(memFn)>(obj);
}
class Foo {
public:
void bar(std::size_t& x, const std::string& s) { x=s.size(); }
};
int main() {
Foo f;
auto c1 = getCallback<&Foo::bar>(&f);
size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
}
还有一种可能。它受到其他答案的启发,但是虽然它们都使用了部分模板特化,但这个仅使用函数模板参数推导。
内部函数接受第二个参数,其类型用于本次推导,其运行时值等于非类型模板参数的编译时值。它在运行时被忽略,特别是它没有被 lambda 捕获。
template <auto memFn, class ClassT, class RetT, class... ArgsT>
inline auto getCallbackInner(ClassT* obj, RetT(ClassT::*)(ArgsT...))
{
return [obj](ArgsT... args)->RetT {
return (obj->*memFn)(std::forward<ArgsT>(args)...);
};
}
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj)
{
return getCallbackInner<memFn, ClassT>(obj, memFn);
}
与其他两个答案一样,这个答案仍然使用 C++17 标准的自动模板参数,因此它在 Visual Studio 中不起作用。很遗憾,但似乎仅使用 C++14 是不可能的。
脚注:
另一个更主观但可能最正确的答案是,我不应该首先尝试将成员函数作为模板参数传递。最初的 getCallbackPtr()
,它只是将一个成员函数指针绑定到 lambda 中,可能比任何其他可能性都更容易被更多的人(以及更多的编译器)理解。通过成员函数指针间接访问的性能成本可能微不足道,而使用模板技巧的维护成本很高,所以我认为我会在实践中实际使用该版本,除非有可证明的性能成本。
上下文
我想将一个成员函数和一个特定的对象包装成一个函数对象(稍后我将使用它作为回调)。我想为不同的成员函数和对象编写一次这个包装函数,特别是因为我的实际 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()
过于冗长且难以使用。
这能做到吗?怎么样?
I would like a function that combines different aspects of the above three functions [...]
如果我理解正确,你想要什么...在我看来这是可能的,但我看到的只是一个复杂的解决方案。
希望其他人可以提出一个更简单的方法,我使用了几个助手:一个仅声明的模板函数 gth1()
从方法指针
Args...
template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;
以及模板 gth2
结构的特化,该结构具有构造和 return lambda 的静态方法(Holt 的更正:谢谢!)
template <typename, typename, auto>
struct gth2;
template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
{
static auto getLambda (ClassT * obj)
{ return [obj](ArgsT ... args)
{ return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
};
现在可以写一个getCallback()
函数如下
template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
{ return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); }
以下是完整的工作示例
#include <iostream>
template <typename, typename, auto>
struct gth2;
template <typename ClassT, typename ... ArgsT, auto memFn>
struct gth2<ClassT, std::tuple<ArgsT...>, memFn>
{
static auto getLambda (ClassT * obj)
{ return [obj](ArgsT ... args)
{ return (obj->*memFn)(std::forward<ArgsT>(args)...); }; }
};
template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;
template <auto memFn, typename ClassT>
auto getCallback (ClassT * obj)
{ return gth2<ClassT, decltype(gth1(memFn)), memFn>::getLambda(obj); }
// Example of use
struct Foo
{ void bar(size_t& x, const std::string& s) { x=s.size(); } };
int main ()
{
Foo f;
auto l { getCallback<&Foo::bar>(&f) };
size_t x;
l(x, "1234567");
std::cout << x << "\n";
}
推导参数的一种简单方法是使用部分模板特化。
在这个例子中,我通过转发非类型成员函数指针 和 来解决问题,它是自定义仿函数的类型,然后返回。
部分专注于类型,剩下的就很简单了。
#include <iostream>
#include <string>
template <auto memFnPtr, class memFn>
struct getCallbackTemplate;
template <auto memFnPtr, class Ret, class ClassT, class... Args>
struct getCallbackTemplate<memFnPtr, Ret(ClassT::*)(Args...)>
{
getCallbackTemplate (ClassT* obj) : m_obj(obj) {}
Ret operator()(Args... args) {
return (m_obj->*memFnPtr)(std::forward<Args>(args)...);
}
ClassT* m_obj;
};
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj) {
return getCallbackTemplate<memFn, decltype(memFn)>(obj);
}
class Foo {
public:
void bar(std::size_t& x, const std::string& s) { x=s.size(); }
};
int main() {
Foo f;
auto c1 = getCallback<&Foo::bar>(&f);
size_t x1; c1(x1, "123"); std::cout << "c1:" << x1 << "\n";
}
还有一种可能。它受到其他答案的启发,但是虽然它们都使用了部分模板特化,但这个仅使用函数模板参数推导。
内部函数接受第二个参数,其类型用于本次推导,其运行时值等于非类型模板参数的编译时值。它在运行时被忽略,特别是它没有被 lambda 捕获。
template <auto memFn, class ClassT, class RetT, class... ArgsT>
inline auto getCallbackInner(ClassT* obj, RetT(ClassT::*)(ArgsT...))
{
return [obj](ArgsT... args)->RetT {
return (obj->*memFn)(std::forward<ArgsT>(args)...);
};
}
template <auto memFn, class ClassT>
auto getCallback(ClassT* obj)
{
return getCallbackInner<memFn, ClassT>(obj, memFn);
}
与其他两个答案一样,这个答案仍然使用 C++17 标准的自动模板参数,因此它在 Visual Studio 中不起作用。很遗憾,但似乎仅使用 C++14 是不可能的。
脚注:
另一个更主观但可能最正确的答案是,我不应该首先尝试将成员函数作为模板参数传递。最初的 getCallbackPtr()
,它只是将一个成员函数指针绑定到 lambda 中,可能比任何其他可能性都更容易被更多的人(以及更多的编译器)理解。通过成员函数指针间接访问的性能成本可能微不足道,而使用模板技巧的维护成本很高,所以我认为我会在实践中实际使用该版本,除非有可证明的性能成本。