从函数中扣除 Return / 参数类型

Deduction of Return / Argument types from function

我有回调对象的类型层次结构。经过大量简化后,它看起来有点像这样:

template <typename Return, typename... Args>
struct Callback
{
  virtual Return Call(Args...) =0;

  void* mCtx;
}; // ctor, virtual dtor omitted

template <class T, typename Return, typename... Args>
struct MemberCallback: Callback<Return, Args...>
{
  Return Call(Args... args) override
  {
    return (static_cast<T*>(mCtx)->*mFn)(args...);
  }
  
  Return(T::*mFn)(Args...);
}; // ctor, specialisation for Return = void omitted

template <typename Return, typename... Args>
struct FunctionPtrCallback: Callback<Return, Args...>
{
  Return Call(Args... args) override
  {
    return mFn(args..., mCtx);
  }
  
  Return(*mFn)(Args..., void*);
}; // ctor, specialisation for Return = void omitted

以及推断 MemberCallback<>s 类型的工厂函数:

template <class T, typename Return, typename... Args>
MemberCallback<T, Return, Args...> MakeCallback(T& object, Return(T::*fn)(Args...))
{
  return MemberCallback<T, Return, Args...>(&object, fn);
} // overload for const methods omitted

我正在尝试为 FunctionPtrCallback<> 设置类似的工厂函数,但是我一直 运行 出错:

天真的方法 FunctionPtrCallback<Return, Args...> MakeCallback(Return(*fn)(Args..., void*), void*) 在未能从 void (ActualTypeUsed,void *) (C2784) 中推断出 Return (__cdecl *)(Args...,void *) 的模板参数后,产生了关于未能找到合适的重载 (C2672) 的抱怨。

编辑:显式指定模板参数 - MakeCallback<void, ActualTypeUsed> 将使其编译, 除了 对于仅采用单个 void* 的函数(MakeCallback<void>()).

(如果我在 MakeCallback 声明中省略 fn 签名中的 void* 参数,它会编译,但会实例化错误的 FunctionPtrCallback 模板 - FunctionPtrCallback<void, ActualTypeUsed, void*> 而不是 FunctionPtrCallback<void, ActualTypeUsed>.)

然后我尝试使用临时 'function traits' 模板,类似于 中建议的模板:

template <typename T>
struct FnTraits;

template <typename Return, typename... Args>
struct FnTraits<Return(Args...)>
{
  FunctionPtrCallback<Return, Args...> MakeCallback(Return(*fn)(Args..., void*), void* data)
  {
    return FunctionPtrCallback<Return, Args...>(fn, data);
  }
};

现在的投诉是关于在我的 FnTraits<decltype(MyCallback)>::MakeCallback(MyCallback, myData) 的嵌套名称说明符中使用不完整的类型。将 MyCallback 的声明放在 FnTraits<> 之前(无论多么不切实际),也无济于事。

1,用简洁的学术术语来说,为什么我的方法是错误的/我对模板推导有什么误解?

2,如果有办法让它工作,从我的回调中自动推导出 ReturnArgs...,那是什么?

(我知道 std::function<>;我宁愿避免使用它。)

编辑:godbolt link

不幸的是,它似乎是一个包扩展,其中包不在列表的末尾,例如Return(*)(Args..., void*),是一个非推导上下文(参见 #8 here;免责声明:我不是语言律师)。这意味着 Args 无法从您示例中的 FooBaz 类型推导出来。

但是,您可以解决此问题,方法是将参数包推导为 include void*,然后稍后删除 void* : https://godbolt.org/z/aTTqbc

// Given Args that doesn't include void*, take the parameter pack
// out of the tuple and put it into the desired FunctionPtrCallback type.
template <typename Return, typename Tuple>
struct MakeCallbackImpl;
template <typename Return, typename... Args>
struct MakeCallbackImpl<Return, std::tuple<Args...>> {
    using type = FunctionPtrCallback<Return, Args...>;
};

// Given Args that *does* include void*, remove it using RemoveLast.
// Args can now be deduced because it appears last in Return(*)(Args...).
template <typename Return, typename... Args>
auto MakeCallback(Return(*fn)(Args...), void* data) {
    return typename MakeCallbackImpl<Return, typename RemoveLast<Args...>::type>::type(fn, data);
}

和助手 RemoveLast,受 this forum thread 启发(但使用 std::tuple_cat 作为便利而不是定义自定义连接):

template<typename First, typename... Rest>
struct RemoveLast {
    using type = decltype(std::tuple_cat(
        std::declval<std::tuple<First>>(),
        std::declval<typename RemoveLast<Rest...>::type>()
    ));
};

template<typename First>
struct RemoveLast<First> {
    using type = std::tuple<>;
};