c++ - 将模板仿函数传递给模板 std::function

标签 c++ templates c++11 c++14

这是一个代码片段:

#include <functional>
#include <iostream>
#include <memory>

template <typename T>
using Callback = std::function<void(const T)>;

template <typename T>
void Call(const T yyy, const Callback<T>& callback) {
    callback(yyy);
}

template <typename T>
class MyCallback {
public:
    explicit MyCallback(const T counter_init) : counter_{counter_init} {}
    void operator ()(const T xxx) {
        std::cout << counter_ << '\t' << xxx << std::endl;
        ++counter_;
    }
private:
    T counter_;
};

int main() {
    const auto callback = std::make_unique<MyCallback<int>>(0);
    Call(111, *callback);  // expected output is "0 \t 111"
    Call(222, *callback);  // expected output is "1 \t 222"
    return 0;
}

Clang 说它无法将 std::functionMyCallback 匹配,g++ 认为 MyCallback 是从 Callback 派生的.

clang++ -std=c++14 main.cpp && ./a.out

g++ -std=c++14 main.cpp && ./a.out

我知道解决此问题的最简单方法是使用模板而不是 Callback,这样 Call 将按以下方式定义:

template <typename T, typename F>
void Call(const T yyy, F&& callback) {
    callback(yyy);
}

但是对于下一个开发者来说,callback 有哪个签名是不清楚的。

有人可以从编译器的角度阐明第一个示例中发生的事情吗?我如何在不应用上述 hack 的情况下解决这个问题?

最佳答案

Call 更改为:

template <class T, class F,
  class R=typename std::result_of<F&(const T&)>::type
>
void Call(const T& yyy, F&& f) {
  f(yyy);
}

现在我们在 yyy 上调用 f,并且(假设您有一个实现 SFINAE 安全 result_of 的 C++11 编译器)重载仅当您可以在 yyy 上调用 f 时才有效。

std::function 不是通用的可调用对象。这是一种将可调用对象类型删除为“使用给定签名调用”、复制可调用对象并销毁可调用对象的方法。

类型删除和类型推导是相反的操作。一步完成这两项通常是设计缺陷的标志。 std::function 应该极少从传入的可调用对象的签名中推导出来。

相反,确定您将如何使用给定的功能。然后键入删除到该使用签名,或者只针对该使用签名进行测试并且根本不要键入删除。

如果您有多个可能的使用签名,请针对它们中的每一个进行测试,标记分派(dispatch)到正确的类型删除路径,并在那里键入删除。

result_of 子句是可选的,但它显着改进了错误消息。它还使错误可以“及早”检测为失败的过载,而不是硬错误。 result_of 子句可以在正文中转换为 static_assert,这将生成更清晰的错误消息,但在重载解析后会“延迟”失败。

另一种阻止对函数进行推导的方法是:

template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<class T>using block_deduction=type_t<tag<T>>;

template <typename T>
using Callback = block_deduction<std::function<void(const T)>>;

现在

template <class T>
void Call(const T& yyy, const Callback<T>& callback) {
  callback(yyy);
}

有效。它仍然会不必要地删除 callback 类型(产生开销)。

通过 struct 的往返并获取 ::type 来阻止推导。在标准下,永远不会推导此类依赖类型。

关于c++ - 将模板仿函数传递给模板 std::function,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29987192/

相关文章:

java - vSphere Web 服务示例失败并显示 "The server sent HTTP status code 200: OK"

gcc - Clang 或 GCC 不支持 codecvt 吗?

c++ - 将函数定义为显式传递或不传递模板参数的不同方法

c++ - 自定义 std::allocator 用于替换了 operator new 的类

C++ 以字节形式保存/加载函数。获取函数的大小

c++ - 如何分 ionic 模板类的定义和声明

c++ - 在 Windows 8.1 中运行 Visual Studio 6 C++

c++ - 返回指向对象的指针(C++ 模板)错误

c++ - 将共享指针存储在 lambda 中以使其保持事件状态

c++ - 如何确保 win-builder 使用 c++11 构建我的包?