从函数参数中推导 C++ 函数模板类型失败

C++ function template type deduction from function parameter fails

我正在编写一个小的信号槽实现,以了解有关模板用法的更多信息。

当我希望编译器从函数的参数中推断出函数模板的模板参数的类型时,我 运行 遇到了问题。

在这里提问之前,我看了几个视频(即 Arthur O'Dwyers 'Template Normal Programming')并通读了几篇文章,但我就是不明白为什么它不起作用。

我构建了一个最小的工作示例:

#include <iostream>
#include <vector>

//Base Event class for passed around events
struct Event
{
  virtual ~Event() = default;

  Event(int keyCode) : keyCode_{keyCode} {}

  int keyCode_;
};

//Event to be passed around
struct KeyPressedEvent final : Event
{
  ~KeyPressedEvent() { std::cout << "KeyPressedEvent dtor " << std::endl; }
  KeyPressedEvent(int keyCode) : Event(keyCode) { std::cout << "KeyPressedEvent param. ctor " << std::endl; }

};

//Class which holds a callback(slot)
struct Window
{
  Window(int y) { std::cout << "window param ctor\n"; }

  void kpEventHandler(KeyPressedEvent& kpEvent)
  {
    std::cout << "non-static member staticKpEventHandler() : " << kpEvent.keyCode_ << "\n";
  }
};    

//Signal which holds connections to callbacks
template<typename Signature> struct Signal;

template<typename Ret, typename ArgType>
struct Signal<Ret(ArgType)>
{
  struct Connection
  {
    using Instance_t = void*;
    using Call_t = void(*)(void*, ArgType&&);

    //template <typename Type>
    Connection(void* instance, Call_t call) :
      instance_{instance}, call_{std::move(call)} {}

    bool operator==(const Connection& other) const noexcept
    {
      bool cmpInstance = instance_ == other.instance_;
      bool cmpCall = call_ == other.call_;
      return cmpInstance && cmpCall;
    }

    Instance_t instance_;
    Call_t call_;
  };

  std::vector<Connection> connections_;

  template<typename C, Ret(C::* func)(ArgType)>
  static void call(void* instancePtr, ArgType&& arg)
  {
    C* instance = static_cast<C*>(instancePtr);
    if (instance != nullptr)
    {
      (instance->*func)(std::forward<ArgType>(arg));
    }
  }

  template<typename C, Ret(C::* func)(ArgType)>
  void connect(C& instance)
  {
    connections_.emplace_back(&instance, std::move(&call<C, func>));
  }

  template<typename C, Ret(C::*func)(ArgType)>
  void disconnect(C& instance)
  {
    Connection conn{&instance, &call<C, func>};
    connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
  }
};

//Test code
int main()
{  
  {
    Window window{5};
    Signal<void(KeyPressedEvent&)> signal;
    signal.connect<&Window::kpEventHandler>(window);    //Error C2974 'Signal<void (KeyPressedEvent &)>::connect': invalid template argument for 'C', type expected 
    signal.disconnect<&Window::kpEventHandler>(window); //Error C2974 'Signal<void (KeyPressedEvent &)>::disconnect': invalid template argument for 'C', type expected  
  }

  std::cin.get();

  return 0;
}

当我将测试代码更改为以下时,显然有效。

signal.connect<Window, &Window::kpEventHandler>(window);    
signal.disconnect<Window, &Window::kpEventHandler>(window); 

当我使用函数指针的 auto 模板参数将 Signal::connect()Signal::disconnect() 更改为以下实现时,我得到了它的工作。

template<auto func, typename C>
void connect(C& instance)
{
  static_assert(std::is_member_function_pointer<decltype(func)>::value,
    "Signal::connect: func is not a pointer to a member function");
  connections_.emplace_back(&instance, std::move(&call<C, func>));
}

template<auto func, typename C>
void disconnect(C& instance)
{
  static_assert(std::is_member_function_pointer<decltype(func)>::value,
    "Signal::disconnect: func is not a pointer to a member function");
  Connection conn{&instance, &call<C, func>};
  connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
}

signal.connect<&Window::kpEventHandler>(window);     //works fine
signal.disconnect<&Window::kpEventHandler>(window);  //works fine

当我在这个解决方案中更改模板参数的顺序时,我也得到一个错误:

template<typename C, auto func>
void disconnect(C& instance)
{
  static_assert(std::is_member_function_pointer<decltype(func)>::value,
    "Signal::disconnect: func is not a pointer to a member function");
  Connection conn{&instance, &call<C, func>};
  connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
}

signal.disconnect<&Window::kpEventHandler>(window); //Error C2974 'Signal<void (KeyPressedEvent &)>::disconnect': invalid template argument for 'C', type expected  

所以我的问题是:

  1. 为什么编译器无法推断出我原始版本中的参数类型?
  2. 为什么当我将 Signal::connect()Signal::disconnect() 实现更改为使用“auto func”时它会起作用?
  3. 为什么在“auto func”解决方案中更改模板参数的顺序时会出现编译器错误?

谢谢你帮助我!

你所有的问题都有相同的答案:模板参数的顺序很重要,要解释一个模板参数,你必须在它之前解释所有个模板参数。

写的时候

template<typename C, Ret(C::* func)(ArgType)>
void connect(C& instance)

template<typename C, auto func>
void disconnect(C& instance)

第一个模板参数可以从instance中推导出来,第二个模板参数无法推导出来,因此必须加以说明。

但是,这就是问题所在,如果你必须解释一个模板参数,你必须在它之前解释所有的模板参数。

所以

signal.connect<&Window::kpEventHandler>(window); 

不起作用,因为 <&Window::kpEventHandler> 解释了 第一个 模板参数,即 C;并且 &Window::kpEventHandler(即值)与第一个模板参数(必须是类型)C 不匹配。

您必须以正确的顺序解释两个 模板参数,因此

signal.connect<Window, &Window::kpEventHandler>(window);    
signal.disconnect<Window, &Window::kpEventHandler>(window); 

如果将不可推导的模板参数放在第一个位置,则不同

template<auto func, typename C>
void connect(C& instance)

这样您就可以解释第一个模板参数(func 值)并让编译器从参数 instance.

中推导出 C

所以你可以这样写

signal.connect<&Window::kpEventHandler>(window);
signal.disconnect<&Window::kpEventHandler>(window);

但是如果你也解释第二个模板参数也可以工作

signal.connect<&Window::kpEventHandler, Windows>(window);
signal.disconnect<&Window::kpEventHandler, Windows>(window);