【问题标题】:C++ overload template with specific signature具有特定签名的 C++ 重载模板
【发布时间】:2017-07-23 08:46:46
【问题描述】:

我有以下:

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 &amp;),因为在当前状态下,我可以执行以下操作:

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

    class NamedCallback {
        void PerformCallback(const Args &);
    };
    NamedCallback myNamedCallback;
    DoStuff(myNamedCallback);
    

有可能吗?

【问题讨论】:

  • 为什么不使用std::function 而不是原始函数指针?
  • @user0042 - 什么原始函数指针?此模板接受任何可调用类型。
  • 因为我不喜欢每次想要DoStuff() 时都构造一个新对象的想法,所以这里有个很好的问题,std::function 会产生多少开销?
  • @Vraiment - 不,这是过早优化的问题。正确的问题是“我是否需要std::function 的类型擦除功能,还是让我的整个函数成为一个足够好的模板,尽管可能出现代码膨胀”?。
  • @StoryTeller 好点,我想你是对的。这解决了我的第一个问题,通过使用 std::function 我可以强制执行回调签名

标签: c++ templates overloading


【解决方案1】:

简单方法

您可以只使用std::function 作为参数或原始指针来解决问题。

第一个版本 (std::function) 稍微好一点,因为允许使用 object functor

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&amp;,因此您想要归档的内容是不可能的。无论如何,不​​允许出现不良演员表(我已经介绍了DoStuff_fn)。

void bad_callback(Args&) {}

// ...
// DoStuff_fn(bad_callback);  // error! cannot cast Args& --> const Args&

高级方法

关于您的问题:

允许(除了前面的选项)传递一个具有特定命名成员函数的对象,该成员函数也将被允许作为回调

这是可能的,但需要一些高级技巧。

我可以在这个问题中向您建议的是一种非常简单的方法(利用 C++17 constexpr if)。

如果您需要更强大的功能或 C++11 兼容,您应该利用 SFINAEhere 一个很好的教程)。

取而代之的是我的方法(保持这个问题的可读性和简单性):

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&amp;) 可转换的函子,否则,它应该具有 perform_op 方法。


Here 演示。


编辑

如果你想利用 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{});
}

提升解决方案 使用 Boost 库:

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

【讨论】:

  • 这不能回答问题的第 2 点。
  • @skypjack 为什么不呢?使用 SFINAE 或 boost 示例,您可以致电 DoStuff_adv(myNamedCallback)。我什至用 C++17 提供了一个例子
  • 是的。无论如何,我怀疑打开此类问题的用户(或对此问题感兴趣的未来读者)几乎不知道如何实现has_special_method。您刚刚通过注入一个新问题解决了一个疑问。我的两分钱。
【解决方案2】:

你的第一个问题可以这样解决:

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

DEMO

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

【讨论】:

    猜你喜欢
    • 2019-06-04
    • 1970-01-01
    • 1970-01-01
    • 2014-08-10
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多