C++具有特定签名的重载模板

C++ overload template with specific signature

本文关键字:重载 C++      更新时间:2023-10-16

>我有以下内容:

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

一切都很好,现在我想在这里做两个改进:

  1. 强制T的签名始终void(const Args &),因为在当前状态下,我可以执行以下操作:

    void badCallback(Args);
    DoStuff(badCallback);
    
  2. 允许
  3. (除了前面的选项之外(传递具有特定命名成员函数的对象,该函数也允许作为回调,例如:

    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.
}

演示

关于第二个可以使用成员函数检测技术解决的问题。存在许多版本,例如此处或此处。