如何在不指定模板参数的情况下将 lambda 与模板参数匹配

How to match lambda to template parameter without specifying template params

本文关键字:参数 lambda 情况下      更新时间:2023-10-16

我正在编写一个消息接收库,并希望编写一个简单的lambda,以便在给定端点上收到消息时调用。

当我尝试一个简单的 lambda 时,它与std::function模板不匹配,因为 lambda 不是确切的类型,这是有道理的。

#include <iostream>
#include <unistd.h>
#include <functional>
#include <memory>
const std::string endpoint1 = "ipc:///tmp/endpoint1.ipc";
const std::string endpoint2 = "ipc:///tmp/endpoint2.ipc";
class ISubscriber {
public:
virtual ~ISubscriber() {};
};
template <typename T>
class Subscriber : public ISubscriber
{
public:
Subscriber(const std::string & endpoint, std::function<void (const T&)> callback);
};
class Context
{
public:
template <typename T>
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
{
// add to subscribers list
}
private:
std::vector<std::unique_ptr<ISubscriber>> _subscribers;
// All the other goo to make messaging work
};
class Type1 {};
class Type2 {};
void test()
{
Context context;
#if 1   // Desired syntax
context.addSubscriberListener(endpoint1, [] (const Type1 & t) {
});
context.addSubscriberListener(endpoint2, [] (const Type2 & t) {
});
#else   // Undesired syntax
context.addSubscriberListener(endpoint1, std::function<void(const Type1 &)>([] (const Type1 & t) {
}));
context.addSubscriberListener(endpoint2, std::function<void(const Type2 &)>([] (const Type2 & t) {
}));
#endif 
}

所需的语法给了我

Test.cpp: In function ‘void test()’:
Test.cpp:43:10: error: no matching function for call to ‘Context::addSubscriberListener(const string&, test()::<lambda(const Type1&)>)’
});
^
Test.cpp:25:11: note: candidate: ‘template<class T> void Context::addSubscriberListener(const string&, std::function<void(const T&)>)’
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
^~~~~~~~~~~~~~~~~~~~~
Test.cpp:25:11: note:   template argument deduction/substitution failed:
Test.cpp:43:10: note:   ‘test()::<lambda(const Type1&)>’ is not derived from ‘std::function<void(const T&)>’
});
^
Test.cpp:45:7: error: no matching function for call to ‘Context::addSubscriberListener(const string&, test()::<lambda(const Type2&)>)’
});
^
Test.cpp:25:11: note: candidate: ‘template<class T> void Context::addSubscriberListener(const string&, std::function<void(const T&)>)’
void addSubscriberListener(const std::string & endpoint, std::function<void(const T &)> callback)
^~~~~~~~~~~~~~~~~~~~~
Test.cpp:25:11: note:   template argument deduction/substitution failed:
Test.cpp:45:7: note:   ‘test()::<lambda(const Type2&)>’ is not derived from ‘std::function<void(const T&)>’
});

对我来说,我是否需要额外的管道,或者管道应该是什么才能使其工作,这对我来说并不明显。

我有哪些选项可以获得所需的语法?

但是你真的需要那个addSubscriberListener()收到std::function

简单地接受通用类型F(用于"功能"(怎么样?

template <typename F>
void addSubscriberListener (const std::string & endpoint, F callback)
{
// add to subscribers list
}

通过这种方式,您可以解决您遇到的先有鸡还是先有蛋的问题:lambda 可以转换为std::function但不是std::function,因此编译器必须知道T类型才能将 lambda 转换为std::function并从中扣除T类型。

无论如何。。。如果您需要维护std::function...不完全是想要的语法,但是...您可以显式T类型

// --------------------------VVVVVVV
context.addSubscriberListener<Type1>(endpoint1, [] (const Type1 & t) {
});
context.addSubscriberListener<Type2>(endpoint2, [] (const Type2 & t) {
});
// --------------------------^^^^^^^

这是打破先有鸡还是先有蛋问题的另一种方法:你显式T类型,所以编译器知道lambda必须用来初始化std::function<void(Type1 const &)>(拳头调用(和std::function<void(Type2 const &)>(第二次调用调用(。

--编辑--

OP精确地表明

在某些时候,我需要声明一种要解压缩的 lambda 参数类型,然后将其传递给 lambda...

OP还精确地可以使用C++17,所以...使用std::function演绎指南从F推导T呢?

鉴于您的callback可调用对象接收一个参数,您可以编写(抱歉:代码未测试(

template <typename F>
void addSubscriberListener (const std::string & endpoint, F callback)
{
using T = std::remove_cv_t<
std::remove_reference_t<
typename decltype(std::function{callback})::argument_type;
// add to subscribers list
}

不幸的是,仅当std::function接收一个参数时,std::function::argument_type才可用,此外,在 C++17 中已弃用并从 C++20 中删除。

所以也许你可以写一个自定义的类型特征,如下所示

template <typename>
struct getTypeFirstArg;
template <typename R, typename T1, typename ... Ts>
struct getTypeFirstArg<std::function<R(T1, Ts...)>>
{ using type = T1; };

并提取T

using T = std::remove_cv_t<
std::remove_reference_t<
typename getTypeFirstArg<decltype(std::function{f})>::type>>;

以下是完整的编译示例

#include <functional>
#include <type_traits>
template <typename>
struct getTypeFirstArg;
template <typename R, typename T1, typename ... Ts>
struct getTypeFirstArg<std::function<R(T1, Ts...)>>
{ using type = T1; };
template <typename F>
void foo (F f)
{
using T = std::remove_cv_t<
std::remove_reference_t<
typename getTypeFirstArg<decltype(std::function{f})>::type>>;
static_assert( std::is_same_v<T, int> );
}
int main ()
{
foo([&](int const &){});
}

另请参阅纪尧姆·拉西科特(Guillaume Racicot(的答案,该答案也应该适用于C++11和C++14。

您应该跳过std::function并直接使用 lambda:

template <typename F>
void addSubscriberListener(std::string const& endpoint, F callback)
{
// add to subscribers list
}

这使得类型推断可用于 lambda 类型

在某些时候,我需要声明一种要解压缩的 lambda 参数类型,然后将其传递给 lambda。

因此,如果我了解您需要考虑 lambda 的参数类型?

这可以使用类似的模式来完成 boost::callable:

template<typename>
struct extract_arg_types {};
template<typename R, typename C, typename Arg>
struct extract_arg_types<R(C::*)(Arg) const> {
using arg_type = Arg;
};

然后你可以在你的类中使用它:

template <typename F>
void addSubscriberListener(std::string const& endpoint, F callback)
{
using T = typename extract_arg_types<decltype(&T::operator())>::arg_type;
// add to subscribers list
}