web-dev-qa-db-fra.com

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

Je ne parviens pas à comprendre la nouvelle syntaxe signal/emplacement (utilisation d'un pointeur sur une fonction membre) dans Qt 5, comme décrit dans Nouvelle syntaxe d'emplacement 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 en essayant 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, tous deux avec -std=c++11.

Qu'est-ce que je fais mal et comment puis-je résoudre le problème?

104
dtruby

Le problème ici est qu’il existe deux signaux portant ce nom: QSpinBox::valueChanged(int) et QSpinBox::valueChanged(QString). A partir de Qt 5.7, des fonctions d’aide sont fournies pour sélectionner la surcharge souhaitée. Vous pouvez ainsi é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 sélectionner en le convertissant 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 pas moyen de contourner cela. La leçon d'aujourd'hui est la suivante: ne surchargez pas vos signaux et vos créneaux! _


Addendum: Ce qui est vraiment ennuyant à propos du 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 s'il s'agit généralement de void (pour les signaux).

Je me suis donc parfois retrouvé en utilisant 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;
    } 
};

Usage:

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

Personnellement, je trouve que ce n'est pas vraiment utile. Je m'attends à ce que ce problème disparaisse de lui-même lorsque Creator (ou votre IDE) insère automatiquement la distribution correcte lors de la réalisation 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'aide ont été ajoutées pour atténuer ce problème, sur le modèle de la solution de contournement décrite ci-dessus. L'assistant principal est qOverload (vous avez également qConstOverload et 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)
202
peppe

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 " 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 une poignée de moyens pour résoudre la surcharge:

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

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

    Cela force connect() à résoudre &QSpinBox::valueChanged dans la surcharge qui prend une int.

    Si vous avez des surcharges non résolues pour l'argument de logement, vous devrez alors fournir le second argument de modèle à connect(). Malheureusement, il n'y a pas de syntaxe à demander pour que le premier soit déduit, vous devez donc fournir les deux. C'est à ce moment 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 à signal sélectionnera la surcharge souhaitée et elle peut maintenant ê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.

  • Utilisez une conversion

    Nous pouvons éviter static_cast ici, car il s'agit simplement d'une contrainte plutôt que de la 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);
    
12
Toby Speight

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

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

sera mieux paraître. : \

7
Newlifer

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.

Ensuite, votre exemple:

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

Devient:

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