Conexión de señales y ranuras sobrecargadas en Qt 5

133

Tengo problemas para familiarizarme con la nueva sintaxis de señal / ranura (usando la función de puntero a miembro) en Qt 5, como se describe en Nueva sintaxis de ranura de señal . Traté de cambiar esto:

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

a esto:

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

pero recibo un error cuando intento compilarlo:

error: no hay función coincidente para la llamada a QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

He intentado con clang y gcc en Linux, ambos con -std=c++11.

¿Qué estoy haciendo mal y cómo puedo solucionarlo?

dtruby
fuente
Si su sintaxis es correcta, entonces la única explicación podría ser que no está vinculando a las bibliotecas Qt5, sino que, por el contrario, Qt4. Esto es fácil de verificar con QtCreator en la página 'Proyectos'.
Matt Phillips
Incluí algunas subclases de QObject (QSpinBox, etc.), por lo que debería haber incluido QObject. Sin embargo, intenté agregar esa inclusión y todavía no se compilará.
dtruby
Además, definitivamente estoy vinculando contra Qt 5, estoy usando Qt Creator y los dos kits con los que estoy probando tienen Qt 5.0.1 listado como su versión Qt.
dtruby

Respuestas:

244

El problema aquí es que hay dos señales con ese nombre: QSpinBox::valueChanged(int)y QSpinBox::valueChanged(QString). Desde Qt 5.7, se proporcionan funciones auxiliares para seleccionar la sobrecarga deseada, para que pueda escribir

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

Para Qt 5.6 y anteriores, debe decirle a Qt cuál desea elegir, lanzándolo al tipo correcto:

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

Lo sé, es feo . Pero no hay forma de evitar esto. La lección de hoy es: ¡no sobrecargue sus señales y ranuras!


Anexo : lo realmente molesto del elenco es que

  1. uno repite el nombre de la clase dos veces
  2. uno tiene que especificar el valor de retorno incluso si es generalmente void(para señales).

Así que a veces me encuentro usando este fragmento de 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;
    } 
};

Uso:

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

Personalmente, no me parece realmente útil. Espero que este problema desaparezca por sí solo cuando Creator (o su IDE) inserte automáticamente el yeso correcto al completar automáticamente la operación de tomar el PMF. Pero mientras tanto ...

Nota: ¡la sintaxis de conexión basada en PMF no requiere C ++ 11 !


Anexo 2 : en Qt 5.7, se agregaron funciones auxiliares para mitigar esto, siguiendo el modelo de mi solución anterior. El ayudante principal es qOverload(también tienes qConstOverloady qNonConstOverload).

Ejemplo de uso (de los documentos):

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)

Anexo 3 : si observa la documentación de cualquier señal sobrecargada, ahora la solución al problema de sobrecarga se indica claramente en los propios documentos. Por ejemplo, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 dice

Nota: Signal valueChanged está sobrecargado en esta clase. Para conectarse a esta señal utilizando la sintaxis del puntero de función, Qt proporciona una ayuda conveniente para obtener el puntero de función como se muestra en este ejemplo:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
Peppe
fuente
1
Ah sí, eso tiene mucho sentido. Supongo que para casos como este donde las señales / ranuras están sobrecargadas, seguiré con la sintaxis anterior :-). ¡Gracias!
dtruby
17
Estaba tan entusiasmado con la nueva sintaxis ... ahora una fría salpicadura de decepción helada.
RushPL
12
Para aquellos que se preguntan (como yo): "pmf" significa "función de puntero a miembro".
Vicky Chijwani
14
Personalmente prefiero la static_castfealdad sobre la sintaxis anterior, simplemente porque la nueva sintaxis permite una verificación en tiempo de compilación de la existencia de la señal / ranura donde la sintaxis anterior fallará en tiempo de ejecución.
Vicky Chijwani
2
Desafortunadamente, no sobrecargar una señal a menudo no es una opción, Qt a menudo sobrecarga sus propias señales. (por ejemplo QSerialPort)
PythonNut el
14

El mensaje de error es:

error: no hay función coincidente para la llamada a QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

La parte importante de esto es la mención del " tipo de función sobrecargado no resuelto ". El compilador no sabe si quieres decir QSpinBox::valueChanged(int)o no QSpinBox::valueChanged(QString).

Hay varias formas de resolver la sobrecarga:

  • Proporcione un parámetro de plantilla adecuado para connect()

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

    Esto obliga connect()a resolver &QSpinBox::valueChangedla sobrecarga que requiere un int.

    Si tiene sobrecargas no resueltas para el argumento de ranura, deberá proporcionar el segundo argumento de plantilla a connect(). Desafortunadamente, no hay sintaxis para pedir que se infiera el primero, por lo que deberá proporcionar ambos. Es entonces cuando el segundo enfoque puede ayudar:

  • Use una variable temporal del tipo correcto

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

    La asignación a signalseleccionará la sobrecarga deseada, y ahora se puede sustituir con éxito en la plantilla. Esto funciona igualmente bien con el argumento de 'ranura', y me parece menos engorroso en ese caso.

  • Usa una conversión

    Podemos evitarlo static_castaquí, ya que es simplemente una coerción en lugar de eliminar las protecciones del lenguaje. Yo uso algo como:

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

    Esto nos permite escribir

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

En realidad, puedes envolver tu ranura con lambda y esto:

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

Se verá mejor. : \

Newlifer
fuente
0

Las soluciones anteriores funcionan, pero resolví esto de una manera ligeramente diferente, usando una macro, así que por si acaso aquí está:

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

Agregue esto en su código.

Entonces, tu ejemplo:

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

Se convierte en:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);
Basile Perrenoud
fuente
2
Soluciones "por encima" de qué? ¡No asuma que las respuestas se presentan a todos en el orden en que las está viendo actualmente!
Toby Speight
1
¿Cómo se usa esto para sobrecargas que requieren más de un argumento? ¿La coma no causa problemas? Creo que realmente necesita pasar los parens, es decir #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun), se usa como CONNECTCAST(QSpinBox, valueChanged, (double))en este caso.
Toby Speight
es una buena macro útil cuando se utilizan paréntesis para múltiples argumentos, como en el comentario de Toby
eyectamenta