4

I want to connect some object's signals derived from an interface class. The connection is done in QWidget::listenToAnimal(AnimalInterface*). This does not work because qt_metacall is not a member of 'AnimalInterface' and static assertion failed: No Q_OBJECT in the class with the signal.

Of course AnimalInterface does not have the Q_OBJECT macro and does not inherit QObject because it is an interface...

I want to connect through the interface class because I do not want to manually retype the same code for Cat and for Dog.

Is it possible to connect the signal the way I want to? Perhaps with templates? Is this perhaps a lambda-specific problem?

header:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class AnimalInterface{
public:
    virtual ~AnimalInterface();

    virtual void makeSound() = 0;

/*signals*/
    virtual void madeSound() = 0;
};
Q_DECLARE_INTERFACE(AnimalInterface,"interface")



class Dog : public QObject, public AnimalInterface
{
    Q_OBJECT
    Q_INTERFACES(AnimalInterface)
public:
    void makeSound();
signals:
    void madeSound();
};


class Cat : public QObject, public AnimalInterface
{
    Q_OBJECT
    Q_INTERFACES(AnimalInterface)
public:
    void makeSound();
signals:
    void madeSound();
};



class Widget : public QWidget
{
    Q_OBJECT
    Cat *cat_;
    Dog *dog_;
public:
    Widget(QWidget *parent = 0);
    ~Widget();
    void listenToAnimal(AnimalInterface *animal);
};

#endif // WIDGET_H

cpp:

#include "widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    dog_ = new Dog;
    cat_ = new Cat;

    listenToAnimal(dog_);
    listenToAnimal(cat_);

    dog_->makeSound();
    cat_->makeSound();
}

void Widget::listenToAnimal(AnimalInterface *animal)
{
    connect(animal, &AnimalInterface::madeSound,
            this,
            [](){
                qDebug()<<"animal made sound";
            });
}

Widget::~Widget()
{

}


void Cat::makeSound()
{
    qDebug()<<"Cat says miaow";
    emit madeSound();
}
void Dog::makeSound()
{
    qDebug()<<"Dog says wuff";
    emit madeSound();
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}
user2366975
  • 4,350
  • 9
  • 47
  • 87

2 Answers2

2

Since you know the derived type at compile type, you can connect to the proper, statically-known QObject-derived type. No need for dynamic casting or anything of the sort. You just don't want the listenToAnimal method to be available for non-AnimalInterface-inheriting types, though, even if it they have a compatible madeSound method:

C++11

#include <type_traits>

template< class T,
          typename = 
            typename std::enable_if<std::is_base_of<AnimalInterface, T>::value>::type >
void listenToAnimal(T * animal) {
  connect(animal, &T::madeSound, this, []{ qDebug() << "animal made sound"; });
}

C++03

template <class T>
void listenToAnimal(T * animal) {
  Q_UNUSED(static_cast<AnimalInterface*>(animal));
  connect(animal, &T::madeSound, this, &Widget::onAnimalMadeSound);
}

You can then use it without having to spell out the type - it's already known to the compiler:

listenToAnimal(dog_);
listenToAnimal(cat_);

If the derived type is not known at compile time, you have to dynamically cast to QObject and connect by name, not by method pointer. It will assert at runtime if you've passed in a wrong type - after all, it's not enough for it to be an instance of AnimalInterface, it also needs to be a QObject instance.

void listenToAnimal(AnimalInterface * animal) {
  auto object = dynamic_cast<QObject*>(animal);
  Q_ASSERT(object);
  connect(object, SIGNAL(madeSound()), this, SLOT(onAnimalMadeSound()));
}

The fact that the type AnimalInterface has a virtual madeSound method is somewhat relevant - it guarantees that the derived class implements the method with such a signature. It doesn't guarantee that the method is a signal, though. So you should probably rethink your design and ask yourself: "What do I gain by using a static type system when I can't really use it for static type checking"?

Most likely you should make any methods that would nominally accept the AnimalInterface*, be parametrized and take a pointer to the concrete class. Modern code generators and linkers will deduplicate such code if type erasure leads to identical machine code.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks for the suggestions, learned something new, but don't you need to remove the `=` after `typename`? Besides, I get the error `no matching function for call to 'Widget::listenToAnimal(Dog*&)'` when I call `listenToAnimal(dog_)`. And as follow up question, do I need to add `std::is_base::value` to the `enable_if` clause, when I construct `Dog` and `Cat` like `AnimalInterface *cat = new Cat`? – user2366975 Aug 28 '16 at 10:00
  • There was a typename missing. Yes, you need `typename = typename ...`, where the first typename is an anonymous template argument. Both forms compile and work as expected. – Kuba hasn't forgotten Monica Aug 28 '16 at 14:27
  • Okay now it does compile. I added a last question in my updated answer. Is it possible to connect the signal by passing a pointer to `AnimalInterface` instead of derived class? According to your answer here http://stackoverflow.com/questions/17943496/declare-abstract-signal-in-interface-class I would say it is not possible, right? – user2366975 Aug 28 '16 at 14:34
  • 1
    @user2366975: that's a separate question. Please limit to one question per post. – jonspaceharper Aug 28 '16 at 19:58
  • @user2366975 It is possible of course, but you have to use RTTI. – Kuba hasn't forgotten Monica Aug 29 '16 at 12:04
  • @user2366975 You should edit the question to clarify what you're after and remove the comment. – Kuba hasn't forgotten Monica Aug 29 '16 at 12:13
0

Found a solution with templates. Did not work the first time I tried, obviously did something wrong first. Here it goes...

Just replace the corresponding parts from the example in the question (and remove definition of listenToAnimal from the source file):

header:

template<class T>
void listenToAnimal(AnimalInterface *animal)
    {
        T *animal_derivate = dynamic_cast<T*>(animal);
        if (animal_derivate){
            connect(animal_derivate, &T::madeSound,
                    this,
                    [](){
                        qDebug()<<"animal made sound";
                    });
        }
    }

cpp:

listenToAnimal<Dog>(dog_);
listenToAnimal<Cat>(cat_);

Update:

After trying Kuba Ober's answer, it seems like this is working best now:

template<typename T>
typename std::enable_if<std::is_base_of<AnimalInterface, T>::value,void>::type
listenToAnimal(T *animal)
    {
        connect(animal, &T::madeSound, this, [](){ qDebug()<<"animal made sound"; });
    }

However, the one point still not working is how to connect if I create an animal like AnimalInterface *bird = new Bird, because it throws the same error that the base class does not have the signal.

user2366975
  • 4,350
  • 9
  • 47
  • 87