将lambda参数完美转发到成员函数,其中成员函数是非类型模板参数

时间:2018-06-06 15:52:41

标签: lambda c++17 variadic-templates member-function-pointers generic-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()令人分心,并且更难以使用。

可以这样做吗?怎么样?

3 个答案:

答案 0 :(得分:2)

推断参数的一种简单方法是使用部分模板特化。

在这个例子中,我通过将非类型成员函数指针的类型转发给自定义仿函数来解决问题,然后返回。

部分专注于该类型,其余部分是直截了当的。

#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";
}

答案 1 :(得分:1)

  

我想要一个结合上述三个函数[...]

的不同方面的函数

如果我理解你想要什么......在我看来这是可能的,但我只看到一个错综复杂的解决方案。

希望其他人可以提出一种更简单的方法,我使用了几个帮助器:一个声明的模板函数gth1()来检测方法指针中的Args...

template <typename ClassT, typename ... ArgsT>
constexpr auto gth1 (void(ClassT::*)(ArgsT...)) -> std::tuple<ArgsT...>;

以及模板gth2 struct的特化,使用静态方法构造并返回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";
 }

答案 2 :(得分:0)

这是另一种可能性。它的灵感来自其他答案,但是虽然这些都使用了一些部分模板专业化,但这只使用了函数模板参数推导。

内部函数接受第二个参数,其类型用于此演绎,其运行时值等于非类型模板参数的编译时间值。它在运行时被忽略,特别是它不被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才有可能。

脚注:

另一个更主观但可能最正确的答案是,我不应该首先尝试将成员函数作为模板参数传递。简单地将成员函数指针绑定到lambda中的原始getCallbackPtr()可能被更多人(以及更多编译器)理解,而不是任何其他可能性。使用模板技巧的维护成本很高时,通过成员函数指针间接的性能成本可能微不足道,所以我认为我实际上会在实践中使用该版本,除非有明显的性能成本。

相关问题