C++具有特定签名的重载模板
C++ overload template with specific signature
>我有以下内容:
struct Args;
template <typename T>
void DoStuff(T&& callback) {
// ... do stuff
MyArgs args = ...
callback(args);
}
这很好,我可以执行以下操作:
void callback(const Args &);
DoStuff(callback);
和:
DoStuff([](const Args &) { ... });
和:
class CallbackClass {
operator()(const Args &);
};
CallbackClass myCallbackClass;
DoStuff(myCallbackClass);
一切都很好,现在我想在这里做两个改进:
强制
T
的签名始终void(const Args &)
,因为在当前状态下,我可以执行以下操作:void badCallback(Args); DoStuff(badCallback);
允许(除了前面的选项之外(传递具有特定命名成员函数的对象,该函数也允许作为回调,例如:
class NamedCallback { void PerformCallback(const Args &); }; NamedCallback myNamedCallback; DoStuff(myNamedCallback);
可能吗?
简单的方法
您可以仅使用std::function
作为参数或原始指针来解决问题。
第一个版本(std::function
(稍微好一点,因为允许使用对象函子。
void DoStuff_fn(std::function<void(const Args&)> callback) { callback(Args{}); }
void DoStuff_raw(void (*callback)(const Args&)) { callback(Args{}); }
struct Functor {
void operator()(const Args&) {}
};
// ...
DoStuff_fn(Functor{}); // right call operator() of object
// DoStuff_raw(Functor{}); // error no conversion available
开销(通过优化(非常小,您可以在演示程序集中看到。无论如何,如果您需要有关这些主题的一些高级答案,最好打开一个特定问题。
关于你的观点:
强制 T 的签名始终为 void(const Args &(,因为在当前状态下我可以执行以下操作:
void badCallback(Args);
DoStuff(badCallback);
Args
是隐式转换为const Args&
的,因此您想要存档的内容是不可能的。无论如何,不允许使用糟糕的演员表(我已经介绍了DoStuff_fn
(。
void bad_callback(Args&) {}
// ...
// DoStuff_fn(bad_callback); // error! cannot cast Args& --> const Args&
<小时 />高级方法
关于您的问题:
允许(除了前面的选项之外(传递具有特定命名成员函数的对象,该函数也允许作为回调
这是可能的,但有一些高级技巧。
在这个问题上,我可以建议你是一种非常简单的方法(利用C++17constexpr if
(。
如果你需要更强大或C++11兼容的东西,你应该利用SFINAE
(这里有一个很好的教程(。
相反,我的方法(保持这个问题的可读性和简单性(:
using Signature = std::function<void(const Args&)>;
template <typename T>
void DoStuff_adv(T&& functor) {
if constexpr (std::is_convertible<T, Signature>::value) {
functor(Args{});
} else {
functor.perform_op(Args{});
}
}
这样,类型T
应该是具有签名的函子void(const Args&
( 可转换的函子,否则,它应该具有perform_op
方法。
这里有一个演示。
>编辑如果你想利用SFINAE,你的方法应该是这样的:
using Signature = std::function<void(const Args&)>;
template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
functor(Args{});
}
template <typename T>
typename std::enable_if<has_special_method<T>::value>::type
DoStuff_adv(T&& functor) {
functor.perfom_op(Args{});
}
使用提升库的加速解决方案:
#include <boost/tti/has_member_function.hpp>
using Signature = std::function<void(const Args&)>;
template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
functor(Args{});
}
BOOST_TTI_HAS_MEMBER_FUNCTION(perform_op)
template <typename T>
typename std::enable_if<
has_member_function_perform_op<void (T::*)(const Args&)>::value>::type
DoStuff_adv(T&& functor) {
functor.perform_op(Args{});
}
你的第一个问题可以这样解决:
#include <type_traits>
#include <tuple>
// the generic signature falls back on the signature of the call operator if present
template <class C>
struct signature : signature< decltype( &std::decay_t<C>::operator() ) > {};
// pointer to member function fall back on the plain function signatures
template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) > : signature< Result ( Args... ) > {};
template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) const > : signature< Result ( Args... ) > {};
// pointer and references to free function fall back on the plain function signatures
template < typename Result , typename... Args >
struct signature< Result (*)(Args...) > : signature< Result ( Args... ) > {};
template < typename Result , typename... Args >
struct signature< Result (&)(Args...) > : signature< Result ( Args... ) > {};
// actual implementation just for pure function signature types
template < typename Result , typename... Args >
struct signature< Result ( Args... ) >
{
static constexpr auto num_args = sizeof...(Args);
template< size_t n >
using argument = typename std::tuple_element< n, std::tuple<Args...> >;
using result_type = Result;
};
template <typename Callable, size_t N >
using argument_t = typename signature<Callable>::template argument<N>::type;
// -------------------------------------------
struct Args {};
template <typename T> // could use enable_if as well
// , typename = std::enable_if_t<std::is_same_v<argument_t<T,0>,const Args&>>>
void DoStuff(T&& callback) {
static_assert(std::is_same_v<argument_t<T,0>,const Args&>, "Callback has the wrong signature");
// ... do stuff
Args args = {};
callback(args);
}
void callback(const Args &) {}
struct CallbackClass {
void operator()(const Args &){}
};
int main()
{
DoStuff(callback);
DoStuff(CallbackClass());
DoStuff([](const Args &) { });
// DoStuff([](Args) { }); // won't compile, triggers static assertion informing the user about the error.
}
演示
关于第二个可以使用成员函数检测技术解决的问题。存在许多版本,例如此处或此处。
- 继承函数的重载解析
- 你能重载对象变量名本身返回的内容吗
- 从父命名空间重载类型
- 使用C++中的模板和运算符重载执行矩阵运算
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 重载操作程序时出错>>用于类中的字符串 memebr
- 一个关于在C++中重载布尔运算符的问题
- 不同翻译单元中不可重载的非内联函数定义
- 为什么使用SFINAE而不是函数重载
- 为什么我不能在 C++ 中的特定函数重载中调用同一函数的任何其他重载?
- 将重载的成员函数传递给函数模板
- c++:可变模板和函数重载
- 重载元组索引运算符-C++
- 如何使用重载的相等(==)运算符向测试用例添加描述
- 重载==不适用于二进制树
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 重载运算符new[]的行为取决于析构函数
- 正在尝试重载二进制搜索树分配运算符
- 重载Singly Linked List中的赋值运算符
- 取消引用运算符不能重载