【问题标题】:How to match lambda to template parameter without specifying template params如何在不指定模板参数的情况下将 lambda 匹配到模板参数
【发布时间】:2019-08-12 18:33:38
【问题描述】:

我正在编写一个消息接收库,并想编写一个简单的 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&)>’
      });

我不清楚是否需要额外的管道,或者管道应该是什么才能使其工作。

我有哪些选择来获得所需的语法?

【问题讨论】:

  • 你可能想要std::function,我想。
  • 我很好奇如果接口只有一个析构函数,你打算如何调用这些订阅者?

标签: c++ templates lambda template-argument-deduction


【解决方案1】:

但你真的需要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&lt;void(Type1 const &amp;)&gt;(第一次调用)和 std::function&lt;void(Type2 const &amp;)&gt;(第二次通话)。

-- 编辑--

OP 精确到

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

OP 也很精确,可以使用 C++17 所以...那么从F 推导出T 怎么样,使用std::function 推导指南?

鉴于您的 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::argument_type 仅在 std::function 接收一个参数时可用,此外,在 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。

【讨论】:

  • 我想我这样做了.. 在某些时候,我需要声明一个要解压的 lambda 参数类型,然后将其传递给 lambda.. 除非有办法一直保持 F通过链,然后得到F的参数类型。
  • @bizaff - 你可以使用 C++17 吗?在这种情况下,您可以使用(在方法内部)std::function 推导指南从 F 推导出 T 类型。如果你愿意,我可以尝试添加一个例子。但仅限于 C++17。
  • 是的,我可以……我很想看看。
  • @bizaff - 答案改进;希望这会有所帮助(但另请参阅 Guillaume Racicot 对 C++17 之前的解决方案的回答)。
【解决方案2】:

你应该跳过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
}

【讨论】:

    猜你喜欢
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 2012-08-08
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多