160 votes

Connexion de signaux et de créneaux surchargés dans Qt 5

J'ai du mal à me familiariser avec la nouvelle syntaxe de signal/slot (utilisant un pointeur vers une fonction membre) de Qt 5, comme décrit dans le document suivant Syntaxe de l'emplacement du nouveau signal . J'ai essayé de changer cela :

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

à ça :

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

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

error : no matching function for call to 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 le réparer ?

290voto

peppe Points 4869

Le problème ici est qu'il y a deux signaux avec ce nom : QSpinBox::valueChanged(int) y QSpinBox::valueChanged(QString) . Depuis Qt 5.7, il existe des fonctions d'aide permettant de sélectionner la surcharge souhaitée, de sorte que vous pouvez écrire

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

Pour Qt 5.6 et antérieurs, vous devez dire à Qt lequel vous voulez choisir, en le coulant dans le bon type :

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

Je sais, c'est laid . Mais il n'y a pas d'autre solution. La leçon d'aujourd'hui est : ne surchargez pas vos signaux et vos créneaux !


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

  1. on répète deux fois le nom de la classe
  2. on doit spécifier la valeur de retour même si elle est généralement void (pour les signaux).

Je me suis donc retrouvé à utiliser parfois cet extrait 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;
    } 
};

Utilisation :

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

Personnellement, je ne le trouve pas vraiment utile. Je pense que ce problème disparaîtra de lui-même lorsque Creator (ou votre IDE) insérera automatiquement le bon casting lors de l'autocomplétion de l'opération de prise du PMF. Mais en attendant...

Note : la syntaxe connect basée sur PMF ne nécessite pas C++11 !


Addendum 2 Dans Qt 5.7, des fonctions d'aide ont été ajoutées pour atténuer ce problème, sur le modèle de ma solution de contournement ci-dessus. La principale fonction d'aide est qOverload (vous avez aussi qConstOverload y qNonConstOverload ).

Exemple d'utilisation (tiré 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 n'importe quel signal surchargé, la solution au problème de surcharge est 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){ /* ... */ });

16voto

Toby Speight Points 3930

Le message d'erreur est le suivant :

error : no matching function for call to QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

La partie la plus importante est la mention de la " type de fonction surchargée non résolue ". Le compilateur ne sait pas si vous voulez dire QSpinBox::valueChanged(int) o QSpinBox::valueChanged(QString) .

Il existe une poignée de moyens de résoudre cette surcharge :

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

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

    Cela oblige connect() pour résoudre &QSpinBox::valueChanged dans la surcharge qui prend un int .

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

  • Utiliser une variable temporaire de type correct

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

    L'affectation à signal sélectionnera la surcharge désirée, et maintenant elle peut être substituée avec succès dans le modèle. Cela fonctionne aussi bien avec l'argument 'slot', et je le trouve moins lourd dans ce cas.

  • Utiliser une conversion

    Nous pouvons éviter static_cast ici, car il s'agit simplement d'une coercition plutôt que d'une suppression 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);

8voto

Newlifer Points 137

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 plus belle.

0voto

Basile Perrenoud Points 2724

Les solutions ci-dessus fonctionnent, mais j'ai résolu le problème d'une manière légèrement différente, en utilisant une macro :

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

Ajoutez ceci dans votre code.

Alors, votre exemple :

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

Devient :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X