Conexão de sinais e slots sobrecarregados no Qt 5

133

Estou tendo problemas para entender a nova sintaxe de sinal / slot (usando o ponteiro para a função de membro) no Qt 5, conforme descrito em Nova sintaxe do slot de sinal . Eu tentei mudar isso:

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

para isso:

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

mas recebo um erro ao tentar compilá-lo:

error: nenhuma função correspondente para chamada para QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Eu tentei com clang e gcc no Linux, ambos com -std=c++11.

O que estou fazendo de errado e como corrigi-lo?

dtruby
fonte
Se sua sintaxe estiver correta, a única explicação pode ser que você não está vinculando às bibliotecas Qt5, mas, por exemplo, Qt4. Isso é fácil de verificar com o QtCreator na página 'Projetos'.
Matt Phillips
Eu incluí algumas subclasses de QObject (QSpinBox etc.), de modo que deveriam ter incluído QObject. Eu tentei adicionar esse include também e ele ainda não será compilado.
Dtruby 28/05
Além disso, eu definitivamente estou ligando ao Qt 5, estou usando o Qt Creator e os dois kits que estou testando com o Qt 5.0.1 estão listados como sua versão do Qt.
dtruby

Respostas:

244

O problema aqui é que existem dois sinais com esse nome: QSpinBox::valueChanged(int)e QSpinBox::valueChanged(QString). No Qt 5.7, existem funções auxiliares para selecionar a sobrecarga desejada, para que você possa escrever

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

Para o Qt 5.6 e versões anteriores, você precisa dizer ao Qt qual deseja escolher, convertendo-o para o tipo certo:

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

Eu sei, é feio . Mas não há maneira de contornar isso. A lição de hoje é: não sobrecarregue seus sinais e slots!


Adendo : o que é realmente irritante no elenco é que

  1. um repete o nome da classe duas vezes
  2. é preciso especificar o valor de retorno, mesmo que seja normalmente void(para sinais).

Então, às vezes, eu me encontrei usando esse trecho do 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), ...)

Pessoalmente, acho que não é realmente útil. Espero que esse problema desapareça quando o Creator (ou seu IDE) inserir automaticamente a conversão correta ao concluir automaticamente a operação de tirar o PMF. Mas enquanto isso ...

Nota: a sintaxe de conexão baseada em PMF não requer C ++ 11 !


Adendo 2 : no Qt 5.7, foram adicionadas funções auxiliares para atenuar isso, modeladas após a minha solução alternativa acima. O ajudante principal é qOverload(você também tem qConstOverloade qNonConstOverload).

Exemplo de uso (dos 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)

Adendo 3 : se você olhar para a documentação de qualquer sinal de sobrecarga, agora a solução para o problema de sobrecarga está claramente definida nos próprios documentos. Por exemplo, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 diz

Nota: O sinal valueChanged está sobrecarregado nesta classe. Para conectar-se a este sinal usando a sintaxe do ponteiro de função, o Qt fornece um auxiliar conveniente para obter o ponteiro de função, conforme mostrado neste exemplo:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
peppe
fonte
1
Ah, sim, isso faz muito sentido. Eu acho que para casos como este, onde os sinais / slots estão sobrecarregados, eu vou ficar com a sintaxe antiga :-). Obrigado!
Dtruby 28/05
17
Eu estava tão empolgado com a nova sintaxe ... agora um resfriado frio de decepção congelante.
RushPL 17/10
12
Para aqueles que se perguntam (como eu): "pmf" significa "ponteiro para a função de membro".
Vicky Chijwani 02/07/2015
14
Pessoalmente, prefiro a static_castfeiúra do que a sintaxe antiga, simplesmente porque a nova sintaxe permite uma verificação em tempo de compilação da existência do sinal / slot em que a sintaxe antiga falharia em tempo de execução.
Vicky Chijwani 02/07/2015
2
Infelizmente, não sobrecarregar um sinal geralmente não é uma opção - o Qt geralmente sobrecarrega seus próprios sinais. (eg QSerialPort)
PythonNut
14

A mensagem de erro é:

error: nenhuma função correspondente para chamada para QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

A parte importante disso é a menção do " tipo de função sobrecarregada não resolvida ". O compilador não sabe se você quer dizer QSpinBox::valueChanged(int)ou QSpinBox::valueChanged(QString).

Existem várias maneiras de resolver a sobrecarga:

  • Forneça um parâmetro de modelo adequado para connect()

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

    Isso força connect()a resolver &QSpinBox::valueChangeda sobrecarga que leva um int.

    Se você tiver sobrecargas não resolvidas para o argumento do slot, precisará fornecer o segundo argumento do modelo connect(). Infelizmente, não há sintaxe para solicitar que o primeiro seja inferido; portanto, você precisará fornecer os dois. É quando a segunda abordagem pode ajudar:

  • Use uma variável temporária do tipo correto

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

    A atribuição para signalselecionará a sobrecarga desejada e agora pode ser substituída com sucesso no modelo. Isso funciona igualmente bem com o argumento 'slot', e acho menos complicado nesse caso.

  • Use uma conversão

    Podemos evitar static_castaqui, pois é simplesmente uma coerção, em vez de remover as proteções da linguagem. Eu 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; }

    Isso nos permite escrever

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

Na verdade, você pode apenas envolver seu slot com lambda e isto:

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

ficará melhor. : \

Newlifer
fonte
0

As soluções acima funcionam, mas eu resolvi isso de uma maneira um pouco diferente, usando uma macro. Portanto, caso esteja aqui:

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

Adicione isso no seu código.

Então, seu exemplo:

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

Torna-se:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);
Basile Perrenoud
fonte
2
Soluções "acima" o que? Não assuma que as respostas são apresentadas a todos na ordem em que você as vê atualmente!
precisa saber é o seguinte
1
Como você usa isso para sobrecargas que exigem mais de um argumento? A vírgula não causa problemas? Eu acho que você realmente precisa passar os parênteses, ie #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- usado como CONNECTCAST(QSpinBox, valueChanged, (double))neste caso.
precisa saber é o seguinte
é uma macro útil bom quando os suportes são usados para vários argumentos, como no de Toby comentário
ejectamenta