【问题标题】:C++ function template type deduction from function parameter fails从函数参数中推导 C++ 函数模板类型失败
【发布时间】:2021-07-01 13:58:28
【问题描述】:

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

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

在问这里之前,我观看了几个视频(即 Arthur O'Dwyers 的“模板常规编程”)并阅读了几篇文章,但我就是不知道为什么它不起作用。

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

#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”解决方案中更改模板参数的顺序时会出现编译器错误?

谢谢你帮助我!

【问题讨论】:

    标签: c++ templates c++17 template-argument-deduction type-deduction


    【解决方案1】:

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

    当你写作时

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

    不起作用,因为&lt;&amp;Window::kpEventHandler&gt; 解释了 first 模板参数,即C;并且&amp;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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-16
      • 2014-04-17
      • 1970-01-01
      • 2015-09-19
      • 2011-07-15
      • 1970-01-01
      • 2018-12-05
      • 1970-01-01
      相关资源
      最近更新 更多