Connexion de signaux et de slots surchargés dans Qt 5

133

J'ai du mal à me familiariser avec la nouvelle syntaxe de signal / slot (en utilisant le pointeur vers la fonction membre) dans Qt 5, comme décrit dans la nouvelle syntaxe de slot de signal . J'ai essayé de changer ceci:

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                 slider, SLOT(setValue(int));

pour ça:

QObject::connect(spinBox, &QSpinBox::valueChanged,
                 slider, &QSlider::setValue);

mais j'obtiens une erreur lorsque j'essaye de le compiler:

erreur: pas de fonction correspondante pour l'appel à QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

J'ai essayé avec clang et gcc sous Linux, les deux avec -std=c++11.

Qu'est-ce que je fais mal et comment puis-je y remédier?

dtruby
la source
Si votre syntaxe est correcte, alors la seule explication pourrait être que vous ne liez pas aux bibliothèques Qt5, mais par exemple Qt4 à la place. Ceci est facile à vérifier avec QtCreator sur la page 'Projets'.
Matt Phillips
J'ai inclus quelques sous-classes de QObject (QSpinBox etc.) de sorte que cela aurait dû inclure QObject. J'ai également essayé d'ajouter cet include et il ne se compilera toujours pas.
dtruby
De plus, je suis définitivement lié à Qt 5, j'utilise Qt Creator et les deux kits que je teste avec les deux ont Qt 5.0.1 répertorié comme leur version Qt.
dtruby

Réponses:

244

Le problème ici est qu'il y a deux signaux avec ce nom: QSpinBox::valueChanged(int)et QSpinBox::valueChanged(QString). À partir de Qt 5.7, des fonctions d'assistance sont fournies pour sélectionner la surcharge souhaitée, vous pouvez donc écrire

connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Pour Qt 5.6 et les versions antérieures, vous devez indiquer à Qt celui que vous voulez choisir, en le castant dans le bon type:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Je sais, c'est moche . Mais il n'y a aucun moyen de contourner cela. La leçon d'aujourd'hui est la suivante: ne surchargez pas vos signaux et vos slots!


Addendum : ce qui est vraiment ennuyeux avec le casting, c'est que

  1. on répète le nom de la classe deux fois
  2. il faut spécifier la valeur de retour même si c'est habituellement void(pour les signaux).

Je me suis donc retrouvé à utiliser parfois cet extrait de code C ++ 11:

template<typename... Args> struct SELECT { 
    template<typename C, typename R> 
    static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { 
        return pmf;
    } 
};

Usage:

connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)

Personnellement, je le trouve pas vraiment utile. Je m'attends à ce que ce problème disparaisse de lui-même lorsque Creator (ou votre IDE) insérera automatiquement la bonne distribution lors de la finalisation automatique de l'opération de prise du PMF. Mais en attendant ...

Remarque: la syntaxe de connexion basée sur PMF ne nécessite pas C ++ 11 !


Addendum 2 : dans Qt 5.7, des fonctions d'assistance ont été ajoutées pour atténuer ce problème, sur le modèle de ma solution de contournement ci-dessus. L'assistant principal est qOverload(vous avez aussi qConstOverloadet qNonConstOverload).

Exemple d'utilisation (à partir de la documentation):

struct Foo {
    void overloadedFunction();
    void overloadedFunction(int, QString);
};

// requires C++14
qOverload<>(&Foo:overloadedFunction)
qOverload<int, QString>(&Foo:overloadedFunction)

// same, with C++11
QOverload<>::of(&Foo:overloadedFunction)
QOverload<int, QString>::of(&Foo:overloadedFunction)

Addendum 3 : si vous regardez la documentation de tout signal surchargé, la solution au problème de surcharge est maintenant clairement indiquée dans la documentation elle-même. Par exemple, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 dit

Remarque: le signal valueChanged est surchargé dans cette classe. Pour se connecter à ce signal en utilisant la syntaxe du pointeur de fonction, Qt fournit une aide pratique pour obtenir le pointeur de fonction comme indiqué dans cet exemple:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
peppe
la source
1
Ah ouais, cela a beaucoup de sens. Je suppose que pour des cas comme celui-ci où les signaux / slots sont surchargés, je m'en tiendrai à l'ancienne syntaxe :-). Merci!
dtruby
17
J'étais tellement excité par la nouvelle syntaxe ... maintenant une froide éclaboussure de déception glaciale.
RushPL
12
Pour ceux qui se demandent (comme moi): "pmf" signifie "pointer to member function".
Vicky Chijwani
14
Personnellement, je préfère la static_castlaideur à l'ancienne syntaxe, simplement parce que la nouvelle syntaxe permet une vérification à la compilation de l'existence du signal / slot où l'ancienne syntaxe échouerait à l'exécution.
Vicky Chijwani
2
Malheureusement, ne pas surcharger un signal n'est souvent pas une option - Qt surcharge souvent ses propres signaux. (par exemple QSerialPort)
PythonNut
14

Le message d'erreur est:

erreur: pas de fonction correspondante pour l'appel à QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

La partie importante de ceci est la mention du « type de fonction surchargé non résolu ». Le compilateur ne sait pas si vous voulez dire QSpinBox::valueChanged(int)ou QSpinBox::valueChanged(QString).

Il existe plusieurs façons de résoudre la surcharge:

  • Fournissez un paramètre de modèle approprié pour connect()

    QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
                                             slider,  &QSlider::setValue);

    Cela oblige connect()à résoudre &QSpinBox::valueChangedla surcharge qui prend un int.

    Si vous avez des surcharges non résolues pour l'argument slot, vous devrez fournir le deuxième argument de modèle à connect(). Malheureusement, il n'y a pas de syntaxe pour demander que le premier soit déduit, vous devrez donc fournir les deux. C'est alors que la deuxième approche peut aider:

  • Utilisez une variable temporaire du type correct

    void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, signal,
                     slider,  &QSlider::setValue);

    L'affectation à signalsélectionnera la surcharge souhaitée, et maintenant elle peut être remplacée avec succès dans le modèle. Cela fonctionne aussi bien avec l'argument «slot», et je le trouve moins encombrant dans ce cas.

  • Utiliser une conversion

    Nous pouvons éviter static_castici, car il s'agit simplement d'une contrainte plutôt que d'un retrait des protections de la langue. J'utilise quelque chose comme:

    // Also useful for making the second and
    // third arguments of ?: operator agree.
    template<typename T, typename U> T&& coerce(U&& u) { return u; }

    Cela nous permet d'écrire

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);
Toby Speight
la source
8

En fait, vous pouvez simplement envelopper votre slot avec lambda et ceci:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
    slider, &QSlider::setValue);

sera meilleure. : \

Newlifer
la source
0

Les solutions ci-dessus fonctionnent, mais j'ai résolu cela d'une manière légèrement différente, en utilisant une macro, donc juste au cas où la voici:

#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)

Ajoutez ceci dans votre code.

Ensuite, votre exemple:

QObject::connect(spinBox, &QSpinBox::valueChanged,
             slider, &QSlider::setValue);

Devient:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);
Basile Perrenoud
la source
2
Des solutions "au-dessus" de quoi? Ne supposez pas que les réponses sont présentées à tout le monde dans l'ordre dans lequel vous les voyez actuellement!
Toby Speight
1
Comment utilisez-vous cela pour les surcharges qui prennent plus d'un argument? La virgule ne pose-t-elle pas de problèmes? Je pense que vous devez vraiment passer les parens, c'est #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)-à- dire - utilisé comme CONNECTCAST(QSpinBox, valueChanged, (double))dans ce cas.
Toby Speight
c'est une belle macro utile lorsque des crochets sont utilisés pour plusieurs arguments, comme dans le commentaire de Toby
ejectamenta