Search code examples
c++templatesc++11multiple-inheritanceambiguous

Ambiguous call to inherited template method


I am working on a simple event system to practice using the stdlib.

I made an EventEmitter<T> class which provides addListener(T) and emitEvent(T).

There's a TestEmitter class that extends EventEmitter<int> and EventEmitter<std::string>. The TestEmitter class has an emitTestEvent method that calls either emitEvent(1337) or emitEvent(std::string("test")).

In main() an instance of TestEmitter is created and two listeners are added for T=int and T=std::string.

I am getting 4 errors that look like this: reference to ‘emitEvent’ is ambiguous. Twice on the emitEvent calls and twice on the addListener calls.

Here's the full output:

main.cpp: In member function ‘virtual void TestEmitter::emitTestEvent()’:
main.cpp:42:4: error: reference to ‘emitEvent’ is ambiguous
main.cpp:23:15: error: candidates are: void EventEmitter<T>::emitEvent(T) [with T = std::basic_string<char>]
main.cpp:23:15: error:                 void EventEmitter<T>::emitEvent(T) [with T = int]
main.cpp:45:4: error: reference to ‘emitEvent’ is ambiguous
main.cpp:23:15: error: candidates are: void EventEmitter<T>::emitEvent(T) [with T = std::basic_string<char>]
main.cpp:23:15: error:                 void EventEmitter<T>::emitEvent(T) [with T = int]
main.cpp: In function ‘int main(int, char**)’:
main.cpp:53:10: error: request for member ‘addListener’ is ambiguous
main.cpp:19:15: error: candidates are: void EventEmitter<T>::addListener(EventEmitter<T>::Listener) [with T = std::basic_string<char>; EventEmitter<T>::Listener = std::function<void(std::basic_string<char>)>]
main.cpp:19:15: error:                 void EventEmitter<T>::addListener(EventEmitter<T>::Listener) [with T = int; EventEmitter<T>::Listener = std::function<void(int)>]
main.cpp:57:10: error: request for member ‘addListener’ is ambiguous
main.cpp:19:15: error: candidates are: void EventEmitter<T>::addListener(EventEmitter<T>::Listener) [with T = std::basic_string<char>; EventEmitter<T>::Listener = std::function<void(std::basic_string<char>)>]
main.cpp:19:15: error:                 void EventEmitter<T>::addListener(EventEmitter<T>::Listener) [with T = int; EventEmitter<T>::Listener = std::function<void(int)>]

I am quite confused as to why I am getting this error, the compiler (g++) clearly knows what T is for either of the calls but it doesn't know which method to call?

I was able to fix the errors on the emitEvent calls by prepending them with EventEmitter<int>:: and EventEmitter<std::string> but I am not sure if this is the right way to fix this problem.

Why are the calls to emitEvent and addListener ambiguous when the compiler knows the type of T? How can I fix this?

I hope I provided enough information, if not let me know. Here's the code:

#include <list>
#include <map>
#include <string>
#include <functional>
#include <iostream>

template<typename T>
class EventEmitter {
private:
        typedef std::function<void(T)> Listener;

protected:
        std::list<Listener> listeners_;

public:
        EventEmitter() {};
        virtual ~EventEmitter() {};

        virtual void addListener(Listener listener) {
                listeners_.push_back(listener);
        };

        virtual void emitEvent(T event) {
                typename std::list<Listener>::iterator it = listeners_.begin();
                for(; it != listeners_.end(); ++it) {
                        (*it)(event);
                }
        };
};

class TestEmitter : public EventEmitter<int>, public EventEmitter<std::string> {
private:
        int count_;

public:
        TestEmitter() {};
        virtual ~TestEmitter() {};

        virtual void emitTestEvent() {
                count_ = (++count_) % 2;
                if(count_ == 0) {
                        EventEmitter<int>::emitEvent(1337);
                }
                else {
                        EventEmitter<std::string>::emitEvent(std::string("test"));
                }
        };
};

int main(int argc, char* argv[]) {
        TestEmitter emitter;

        emitter.addListener([](int event) {
                std::cout << "Hello: " << event << std::endl;
        });

        emitter.addListener([](std::string event) {
                std::cout << "Bye: " << event << std::endl;
        });

        for(int i = 0; i < 10; i++) {
                emitter.emitTestEvent();
        }

        return 0;
}

Thanks :)


Solution

  • Your class inherits two different other classes. Now, there are two methods: EventEmitter::emitTestEvent and EventEmitter::emitTestEvent

    Which one is supposed to be called? This is not clear in itself.

    You can do the following:

    emitter.EventEmitter<int>::emitEvent(/* ... */);
    

    However, you might overthink your design... What should eventEmit do, when the user of your class calls it? Then, design your classes accordingly, such that they offer this behaviour.

    Edit: Another solution can be found by means of using, e.g.:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class A
    {
    public:
        void foo(int) {cout << "A::foo";}
    };
    
    class B
    {
    public:
        void foo(string) {cout << "B::foo";}
    };
    
    class C : public A, public B
    {
    public:
        using A::foo;
        using B::foo;
    };
    
    int main() {
        C c;
        c.foo(10);
        c.foo("hi");
        return 0;
    }