将模板仿函数传递给模板 std::function
Pass template functor to a template std::function
这是一个代码片段:
#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 派生自 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);
}
但下一位开发人员不清楚 回调 具有哪个签名。
有人可以从编译器的角度阐明第一个示例中发生的事情吗?我如何在不应用上面描述的 hack 的情况下解决这个问题?
std::function<void(const T)>
可以用*callback
值构造。问题是 Call
是一个函数模板。编译器无法从 Call(111, *callback);
自动实例化 void Call(const int yyy, const Callback<int>& 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
构造的。 Call
中的函数参数 const Callback<int>& callback
然后绑定到这个临时的。同样,这都是关于转换的。函数签名与调用不完全匹配。
将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 编译器)重载仅在您可以调用 f
在 yyy
.
std::function
不是通用的可调用对象。这是一种将可调用对象类型擦除到 "being called with a given signature"、复制可调用对象并销毁可调用对象的方法。
类型擦除和类型推导是相反的操作。一步完成这两项通常是设计缺陷的标志。 std::function
应该极少从传入的可调用对象的签名中推导出来。
相反,确定您将如何使用给定的功能。然后键入擦除到那个使用签名,或者只是测试那个使用签名并且根本不要键入擦除。
如果您有多个可能的使用签名,请针对每个使用签名进行测试,标记分派到正确的类型擦除路径,然后在那里键入擦除。
result_of
子句是可选的,但它显着改进了错误消息。它还使错误可检测为 "early" 作为失败的重载,而不是作为硬错误。 result_of
子句可以在正文中转换为 static_assert
,这将生成更清晰的错误消息,但在重载解析后会失败 "late"。
另一种阻止对 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
的往返并获得 ::type
来阻止演绎。在标准下,永远不会推导此类依赖类型。
来自[temp.arg.explicit]:
Implicit conversions (Clause 4) will be performed on a function argument to convert it to the type of the
corresponding function parameter if the parameter type contains no template-parameters that participate
in template argument deduction. [ Note: Template parameters do not participate in template argument
deduction if they are explicitly specified. For example,
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);
}
这是一个代码片段:
#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 派生自 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);
}
但下一位开发人员不清楚 回调 具有哪个签名。
有人可以从编译器的角度阐明第一个示例中发生的事情吗?我如何在不应用上面描述的 hack 的情况下解决这个问题?
std::function<void(const T)>
可以用*callback
值构造。问题是 Call
是一个函数模板。编译器无法从 Call(111, *callback);
自动实例化 void Call(const int yyy, const Callback<int>& 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
构造的。 Call
中的函数参数 const Callback<int>& callback
然后绑定到这个临时的。同样,这都是关于转换的。函数签名与调用不完全匹配。
将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 编译器)重载仅在您可以调用 f
在 yyy
.
std::function
不是通用的可调用对象。这是一种将可调用对象类型擦除到 "being called with a given signature"、复制可调用对象并销毁可调用对象的方法。
类型擦除和类型推导是相反的操作。一步完成这两项通常是设计缺陷的标志。 std::function
应该极少从传入的可调用对象的签名中推导出来。
相反,确定您将如何使用给定的功能。然后键入擦除到那个使用签名,或者只是测试那个使用签名并且根本不要键入擦除。
如果您有多个可能的使用签名,请针对每个使用签名进行测试,标记分派到正确的类型擦除路径,然后在那里键入擦除。
result_of
子句是可选的,但它显着改进了错误消息。它还使错误可检测为 "early" 作为失败的重载,而不是作为硬错误。 result_of
子句可以在正文中转换为 static_assert
,这将生成更清晰的错误消息,但在重载解析后会失败 "late"。
另一种阻止对 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
的往返并获得 ::type
来阻止演绎。在标准下,永远不会推导此类依赖类型。
来自[temp.arg.explicit]:
Implicit conversions (Clause 4) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction. [ Note: Template parameters do not participate in template argument deduction if they are explicitly specified. For example,
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);
}