-1

That's one cryptic title, but I couldn't think of a way to better describe my problem.

I'm writing up a small event system using std::function and std::bind and so far things are working pretty nicely. The problem I'm experiencing now, however, is that I'm trying to call a function with a parameter that is inherited of the type specified in the functor.

Below I'm trying to call the bound function using a parameter that is not of the type Event as specified in the functor but of the type SocketConnectedEvent. This results in the compiler throwing Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...)', and I assume I need to use templates. However, I would like to be able to use the functor in an implementation-independent manner, where it is possible to use it with any class derived from Event. I'd like to avoid having to hard-code all possible event types. I know I could do that using templates, but then I'd be restricted to as many values I'd be willing to template. The class binding the event functions should be able to bind as many different parameters with types derived from Event as it wants.

Is there any way I can achieve this?

I'm still pretty new to C++ so forgive me for any errors. Thanks!

EDIT:

Okay so here's my original code, with the irrelevant parts left out. The reason I did not post it was because it's a rather big project and I thought it might make the question too confusing. Even this code might not work because I had to abstract it. This is not to confuse anyone, but if I didn't I'd have to practically include the entire project. But anyway:

SystemHandler.h

#ifndef SYSTEMHANDLER_H
#define SYSTEMHANDLER_H

#include <QB/Console/Console.h>
#include <QB/Network/Socket.h>
#include <QB/Network/SocketConnectedEvent.h>

class SystemHandler {
public:
    SystemHandler();

    void onSocketConnected(const QB::Network::SocketConnectedEvent &);
};

#endif

SystemHandler.cpp

#include "SystemHandler.h"

SystemHandler::SystemHandler() {
    QB::Network::Socket socket = QB::Network::Socket(QB::Network::AddressFamily_InterNetwork, QB::Network::SocketType_Stream, IPPROTO_TCP);
    socket.subscribe("SocketConnected", std::bind(&SystemHandler::onSocketConnected, this, std::placeholders::_1)); // This is where things go wrong

    socket.connect("www.google.com", 80);

    QB::Console::readLine();
}

void SystemHandler::onSocketConnected(const QB::Network::SocketConnectedEvent & event) {
    QB::Console::writeLine("SUCCESS!");
    QB::Console::writeLine(event.getHost());
}

EventSource.h

#ifndef QB_EVENTS_EVENTSOURCE_H
#define QB_EVENTS_EVENTSOURCE_H

#include <functional>

#include <map>
#include <vector.h>
#include <string>
#include "Event.h"

namespace QB {
    namespace Events {
        class Event;

        class EventSource {
        public:
            typedef std::function<void(const Event &)> Handler;

            EventSource();

            void dispatch(const String &, const Event &);
            void subscribe(const String &, const Handler &);

        private:
            std::map<String, std::vector<Handler>> subscribers;
        };
    }
}

#endif

EventSource.cpp

#include "EventSource.h"

namespace QB {
    namespace Events {
        EventSource::EventSource() {
        }

        void EventSource::dispatch(const std::string & event, const Event & data) {
            // Check if there are subscribers
            if (subscribers.find(event) != subscribers.end()) {
                // Get handlers
                std::vector<Handler> handlers = subscribers[event];

                // Loop event handlers and notify them
                for (Handler handler : handlers) {
                    handler(data);
                }
            }
        }

        void EventSource::subscribe(const std::string & event, const Handler & handler) {
            if (subscribers.find(event) != subscribers.end()) {
                subscribers[event].add(handler);
            } else {
                std::vector<Handler> handlers = std::vector<Handler>();

                handlers.push_back(handler);
                subscribers.insert(std::make_pair(event, handlers));
            }
        }
    }
}

Socket.h

#ifndef QB_NETWORK_SOCKET_H
#define QB_NETWORK_SOCKET_H

#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <ws2tcpip.h>

#include <string>
#include "../Events/EventSource.h"
#include "Network.h"
#include "SocketConnectedEvent.h"

namespace QB {
    namespace Events {
        class EventSource;
    }

    namespace Network {
        class SocketConnectedEvent;

        class Socket : public Events::EventSource {
        public:
            Socket(const AddressFamily &, const SocketType &, const IPPROTO &);

            void connect(const std::string &, const int &);

        private:
            SOCKET value;
        };
    }
}

#endif

Socket.cpp

#include "Socket.h"

namespace QB {
    namespace Network {
        Socket::Socket(const AddressFamily & addressFamily, const SocketType & socketType, const IPPROTO & protocolType) {
            // Socket initialization code
        }

        void Socket::connect(const std::string & host, const int & port) {
            // Irrelevant socket connect code

            dispatch("SocketConnected", SocketConnectedEvent(host, port));
        }
    }
}

Event.h

#ifndef QB_EVENTS_EVENT_H
#define QB_EVENTS_EVENT_H

namespace QB {
    namespace Events {
        class Event {
        public:
            Event();
        };
    }
}

#endif

Event.cpp

#include "Event.h"

namespace QB {
    namespace Events {
        Event::Event() {
        }
    }
}

SocketConnectedEvent.h

#ifndef QB_EVENTS_SOCKETCONNECTEDEVENT_H
#define QB_EVENTS_SOCKETCONNECTEDEVENT_H

#include <string>
#include "../Events/Event.h"

namespace QB {
    namespace Events {
        class Event;
    }

    namespace Network {
        class SocketConnectedEvent : public Events::Event {
        public:
            SocketConnectedEvent(const std::string & host, const int & port);

            const std::string getHost() const;
            const int getPort() const;

        private:
            std::string host;
            int port;
        };
    }
}

#endif

SocketConnectedEvent.cpp

#include "SocketConnectedEvent.h"

namespace QB {
    namespace Network {
        SocketConnectedEvent::SocketConnectedEvent(const std::string & host, const int & port) {
            this->host = host;
            this->port = port;
        }

        const std::string SocketConnectedEvent::getHost() const {
            return host;
        }

        const int SocketConnectedEvent::getPort() const {
            return port;
        }
    }
}

I hope that cleared up the confusion.

Qub1
  • 1,154
  • 2
  • 14
  • 31
  • Don't post here code *off the top of your head*. Post code that you've (tried) to compile and observed to reproduce the problem. – eerorika Oct 27 '15 at 12:55
  • That code is obviously non-compilable, and I wonder what the real code is. If I got the general idea right, this should be something within the domain of double dispatch. – SergeyA Oct 27 '15 at 13:27
  • 1
    This isn't horseshoes. Your problem is that your code does not work; posting code that does not work for reasons other than your problem requires that we read your mind and figure out what errors are real, and what errors are irrelevant. – Yakk - Adam Nevraumont Oct 27 '15 at 14:08
  • Normally if you derive a family of classes from a common base, you access their functionality through virtual functions (and pass these objects by referencce). – n. m. could be an AI Oct 27 '15 at 14:21
  • I've added the original code. – Qub1 Oct 27 '15 at 16:19

1 Answers1

0

The problem I'm experiencing now, however, is that I'm trying to call a function with a parameter that is inherited of the type specified in the functor.

Your problem is not with calling with a parameter that of derived type. It's fine to do that.

Besides a number of syntax errors, your problem is that std::bind(&Test::onSpecializedEvent, this, std::placeholders::_1) cannot be assigned to a variable of type std::function<void(const Event &)>, because the arguments do not match. After binding this, onSpecializedEvent expects a const SpecializedEvent & while a functor that can be stored in std::function<void(const Event &)> must be able to accept any const Event &.

Consider what would happen if you could do that and called handler(not_the_specialized_event); where not_the_specialized_event is an instance of another derivative of Event. Calling handler would seem to be OK because it's std::function<void(const Event &)>. But the call would be handled by onSpecializedEvent which expects a const SpecializedEvent&. A disaster! There wouldn't be any type safety.

The argument types don't need to be exactly the same. Covariant arguments are possible too. For example, if onSpecializedEvent accepted const Event&, then the functor could be stored in a std::function<void(const SpecializedEvent &)>. That's because a functor that can handle any const Event&, can also handle it's derivatives. But that simply cannot work the other way around as you're attempting.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • "It's fine to do that." If you're OK with object slicing, that is. – n. m. could be an AI Oct 27 '15 at 14:23
  • @n.m. There will be no slicing when the functor takes the object by reference. – eerorika Oct 27 '15 at 14:35
  • Okay thank you for the explanation on why it doesn't work, but is there any way to achieve what I'm trying to do while keeping the code flexible? (I've added the original code to the original question) – Qub1 Oct 27 '15 at 16:30