如何传递lambda, function object或std::function作为模板参数并控制它们的参数

how to pass lambda, function object or std::function as template arguments and control their arguments

本文关键字:参数 function 控制 lambda 何传递 object std      更新时间:2023-10-16

我想实现一些协议作为一个类模板,raw_write和raw_read函数作为模板参数。两个原始函数都有严格定义的接口:

int raw_write(uint8_t *src, size_t len);
int raw_read(uint8_t *dst, size_t maxlen);

是否有任何方法来控制这个接口与编译错误,当有人试图传递,例如:

int raw_write2(uint16_t *src, size_t len);

我应该传递模板参数作为指定类型的对象还是作为模板实现内部实例化的类型名称?

认为这是使用std::function存储可调用对象(不需要使用已定义的template类)的理想(或至少一个)解决方案:

#include <iostream>
#include <functional>
struct protocol_callbacks
{
    using func_t = std::function<int(uint8_t*, size_t)>;
    protocol_callbacks(func_t a_reader, func_t a_writer) :
        reader(a_reader),
        writer(a_writer) {}
    func_t reader;
    func_t writer;
};
int writer(uint8_t*, size_t) { return 0; }
int reader(uint8_t*, size_t) { return 0; }
int bad_writer(uint16_t*, size_t) { return 0; }
int main ()
{
    protocol_callbacks pc1(reader, writer);
    protocol_callbacks pc2([](uint8_t*, size_t) { return 0; },
                           [](uint8_t*, size_t) { return 0; });
    //protocol_callbacks pc3(bad_writer, reader);
}

使用bad_writer会导致编译失败(不使用bad_writer http://ideone.com/hG7tqc和使用bad_writer http://ideone.com/roMJgM)。

您可以使用SFINAE和一些trait类来完成此操作。

我认为最好的方法是期望有一个调用兼容的函子——这也恰好更容易。

#include <utility>
#include <type_traits>
template<typename T, bool=true>
struct raw_write_compatible: std::false_type {};
template<typename T, bool=true>
struct raw_read_compatible: std::false_type {};
template<typename T>
struct raw_write_compatible<
  T,
  std::is_convertable<
    decltype(
      std::declval<T&>()(
        std::declval<uint8_t *>(),
        std::declval<size_t>()
      )
    ),
    int
  >::value
>: std::true_type {};
template<typename T>
struct raw_read_compatible<
  T,
  std::is_convertable<
    decltype(
      std::declval<T&>()(
        std::declval<uint8_t *>(),
        std::declval<size_t>()
      )
    ),
    int
  >::value
>: std::true_type {};

这里的要点是,如果T的一个实例可以用签名(uint8_t*, size_t)求值,并且返回类型可以转换为int,那么raw_read_compatible< T >::value就是true

(顺便说一句,您的"write"函数签名可能应该使用指向const uint8_t的指针,因为它不会修改该参数。)

你可以这样使用:

template<typename Reader, typename Writer>
typename std::enable_if<
  raw_read_compatible<Reader>::value && raw_write_compatible<Writer>::value,
  bool // return value of do_some_io_stuff
>::type do_some_io_stuff( Reader const& reader, Writer const& writer ) {
  return true;
}
如果reader/writer可以按你想要的方式调用,

do_some_io_stuff将匹配。

这样做的好处是,当您试图传入不兼容的lambda或函数指针或函子时,do_some_io_stuff无法匹配,而不是匹配然后编译失败。理论上,这可以让你重写一些东西。

上述解决方案需要具有体面的c++ 11支持的编译器:例如,MSVC2012不能与上述一起工作(它在其单词中缺乏"表达式SFINAE")。

一个更简单的解决方案是只使用std::function< int(uint8_t*, size_t) >,但这有两个代价:首先,它在每次调用时都有运行时成本(大致相当于virtual方法调用——所以与io相比没有那么高)——真正的成本是在函数调用边界上阻塞优化。第二,在我的经验中,你可以得到一些编译失败,而不是无法匹配签名错误(我不确定c++ 11标准是否指定std::function的"lambda"构造函数应该只在传递兼容类型时匹配,但我认为我见过失败的实现)。

std::function解决方案的优点是它更简单,它允许将实现放在一个单独的文件中,并且意图更容易理解。