Tremah
Tremah

Reputation: 105

C++ function template type deduction from function parameter fails

I am writing a small signal-slot implementation to learn more about the usage of templates.

I ran into a problem when I want the compiler to deduce the type of a template parameter of a function template from the function's parameter.

Before asking here, I watched several videos (i.e Arthur O'Dwyers 'Template Normal Programming') and read through several articles but i just can't figure out why it isn't working.

I have built a minimal working example:

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

When I change the test code to the following, it obviously works.

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

I got it to work when I change Signal::connect() and Signal::disconnect() to the following implementation using an auto template parameter for the function pointer.

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

When I change the order of the template parameters in this solution, I get an error as well:

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  

So my questions are:

  1. Why can't the compiler deduce the types of the parameters in my original version?
  2. Why does it work when I change the Signal::connect() and Signal::disconnect() implementation to use 'auto func'?
  3. Why do I get a compiler error when I change the order of the template parameters in the 'auto func' solution?

Thank you for helping me!

Upvotes: 1

Views: 129

Answers (1)

max66
max66

Reputation: 66200

All your questions have the same answer: the order of template parameter is important and, to explicate a template parameter, you have to explicate all template parameters before it.

When you write

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

or

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

the first template parameter can be deduced from instance, the second one can't be deduced so has to be explicated.

But, this is the problem, if you have to explicate a template parameter, you must explicate all template parameter before it.

So

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

doesn't works because <&Window::kpEventHandler> explicate the first template parameter, that is C; and a &Window::kpEventHandler (that is a value) doesn't matches C, the first template argument (that has to be a type).

You have to explicate both template parameters, in the correct order, so

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

Different if you place the non-deducible template parameter in first position

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

This way you can explicate the first template parameter (the func value) and let the compiler to deduce C from the argument instance.

So you can write

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

but works also if you explicate the second template parameter too

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

Upvotes: 3

Related Questions