将模板函子传递给模板 std::function

Kos*_*nov 3 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;
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

但是下一个开发人员不清楚回调有哪个签名。

有人可以从编译器的角度澄清第一个示例中发生了什么,以及如何在不应用我上面描述的 hack 的情况下解决这个问题?

Yak*_*ont 5

更改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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

如果您有多个可能的使用签名,请针对每个签名进行测试,将分派标记到正确的类型擦除路径,然后在那里键入擦除。

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

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

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)>>;
Run Code Online (Sandbox Code Playgroud)

现在

template <class T>
void Call(const T& yyy, const Callback<T>& callback) {
  callback(yyy);
}
Run Code Online (Sandbox Code Playgroud)

作品。它仍然会callback不必要地擦除类型(产生开销)。

通过往返 astruct并获得::type. 在标准下,永远不会推导出这样的依赖类型。