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

时间:2015-05-01 12:53:23

标签: 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 :: function MyCallback 相匹配,而g ++认为 MyCallback 来自回调

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);
}

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

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

3 个答案:

答案 0 :(得分:4)

std::function<void(const T)>可以使用*callback进行价值构建。问题是Call是一个功能模板。编译器无法从void Call(const int yyy, const Callback<int>& callback)自动实例化Call(111, *callback);,因为它与调用不匹配。要使用Call(111, *callback);调用实例化,必须进行转换。但是自动模板参数推导不考虑这种转换。它只考虑完全匹配。您可以手动实例化它。因此,纠正代码的一种方法是改变

Call(111, *callback);  // expected output is "0 \t 111"
Call(222, *callback);  // expected output is "1 \t 222"

Call<int>(111, *callback);  // expected output is "0 \t 111"
Call<int>(222, *callback);  // expected output is "1 \t 222"

因此,如果首先使用std::function<void(const T)>构造值,则为临时*callback。然后const Callback<int>& callback中的函数参数Call绑定到此临时值。同样,这完全是关于转换。功能签名与呼叫不完全匹配。

答案 1 :(得分:4)

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);
}

现在我们在f上调用yyy,并且(假设你有一个C ++ 11编译器实现了SFINAE安全result_of),只有你可以调用{{1在f上。

yyy不是通用的可调用对象。这是一种键入 - 擦除callables到“使用给定签名调用”的方法,复制callable,并销毁callable。

类型擦除和类型推导是相反的操作。一步到位通常是设计缺陷的标志。 std::function应该极少从传入的可调用的签名中推断出来。

相反,确定如何使用给定的函数。然后键入 - 擦除到使用签名,或者只测试该使用签名,并且根本不进行类型擦除。

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

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

阻止static_assert扣除的另一种方法是:

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)>>;

的工作原理。它仍然会不必要地删除template <class T> void Call(const T& yyy, const Callback<T>& callback) { callback(yyy); } 类型(产生的开销)。

通过callback进行往返并获得struct来阻止扣减。根据标准,从不推断出这种依赖类型。

答案 2 :(得分:2)

来自[temp.arg.explicit]:

  

将对函数参数执行隐式转换(第4条),以将其转换为类型   如果参数类型不包含参与的模板参数,则对应的函数参数   在模板参数推导中。 [注意:模板参数不参与模板参数   扣除是否明确指定。例如,

template<class T> void f(T);

class Complex {
    Complex(double);
};

void g() {
    f<Complex>(1); // OK, means f<Complex>(Complex(1))
}
     

-end note]

在您的情况下,const Callback<T>&确实包含参与模板参数推断的模板参数,因此没有隐式转换(即MyCallback<int>std::function<void(const int)>)是允许的。

为了使用std::function,您必须让callback参数不参与任何模板参数推断。那就是:

Call<int>(111, *callback); // no argument deduction done here

或者,您可以自己推断callback,不需要类型擦除:

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

最后,如果您真的想要删除类型,可以在Callback内部手动构建Call

template <typename T, typename F>
void Call(const T yyy, F&& f) {
    Callback<T> callback(std::forward<F>(f));
    callback(yyy);
}