Podłączanie przeciążonych sygnałów i gniazd w Qt 5

138

Mam problem z opanowaniem nowej składni sygnału / gniazda (przy użyciu wskaźnika do funkcji składowej) w Qt 5, zgodnie z opisem w nowej składni gniazda sygnału . Próbowałem to zmienić:

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

do tego:

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

ale pojawia się błąd, gdy próbuję go skompilować:

błąd: brak funkcji pasującej do wywołania QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Próbowałem z clang i gcc w Linuksie, oba z -std=c++11.

Co robię źle i jak mogę to naprawić?

dtruby
źródło
Jeśli twoja składnia jest poprawna, jedynym wyjaśnieniem może być to, że nie łączysz się z bibliotekami Qt5, ale np. Qt4. Można to łatwo zweryfikować za pomocą QtCreator na stronie „Projekty”.
Matt Phillips
Dołączyłem kilka podklas QObject (QSpinBox itp.), Więc powinien zawierać QObject. Próbowałem jednak dodać to dołączenie i nadal się nie kompiluje.
dtruby
Ponadto zdecydowanie łączę się z Qt 5, używam Qt Creator, a dwa testowane zestawy mają Qt 5.0.1 wymieniony jako ich wersja Qt.
dtruby

Odpowiedzi:

250

Problem polega na tym, że istnieją dwa sygnały o tej nazwie: QSpinBox::valueChanged(int)i QSpinBox::valueChanged(QString). Od Qt 5.7 dostępne są funkcje pomocnicze do wybierania żądanego przeciążenia, dzięki czemu można pisać

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

W przypadku Qt 5.6 i wcześniejszych musisz powiedzieć Qt, który chcesz wybrać, rzucając go na odpowiedni typ:

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

Wiem, to brzydkie . Ale nie da się tego obejść. Dzisiejsza lekcja brzmi: nie przeciążaj swoich sygnałów i slotów!


Dodatek : to, co naprawdę denerwuje w obsadzie, to to

  1. jeden powtarza nazwę klasy dwukrotnie
  2. należy określić wartość zwracaną, nawet jeśli jest to zwykle void(dla sygnałów).

Więc czasami używam tego fragmentu kodu 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;
    } 
};

Stosowanie:

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

Osobiście uważam, że nie jest to przydatne. Spodziewam się, że ten problem sam zniknie, gdy Kreator (lub twoje IDE) automatycznie wstawi odpowiednią obsadę podczas automatycznego uzupełniania operacji pobierania PMF. Ale w międzyczasie ...

Uwaga: składnia połączenia oparta na PMF nie wymaga C ++ 11 !


Dodatek 2 : w Qt 5.7 zostały dodane funkcje pomocnicze w celu złagodzenia tego problemu, wzorowane na moim obejściu powyżej. Głównym pomocnikiem jest qOverload(masz też qConstOverloadi qNonConstOverload).

Przykład użycia (z dokumentacji):

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)

Dodatek 3 : jeśli spojrzysz na dokumentację dowolnego przeciążonego sygnału, teraz rozwiązanie problemu przeciążenia jest jasno określone w samych dokumentach. Na przykład https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 mówi

Uwaga: Signal valueChanged jest przeciążony w tej klasie. Aby połączyć się z tym sygnałem przy użyciu składni wskaźnika funkcji, Qt zapewnia wygodny pomocnik do uzyskiwania wskaźnika funkcji, jak pokazano w tym przykładzie:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
peppe
źródło
1
Ach tak, to ma dużo sensu. Myślę, że w przypadkach takich jak ten, w których sygnały / gniazda są przeciążone, po prostu pozostanę przy starej składni :-). Dzięki!
dtruby
17
Byłem tak podekscytowany nową składnią ... teraz zimna fala lodowatego rozczarowania.
RushPL
12
Dla tych, którzy się zastanawiają (jak ja): „pmf” oznacza „wskaźnik do funkcji składowej”.
Vicky Chijwani
14
Osobiście wolę static_castbrzydotę niż starą składnię, po prostu dlatego, że nowa składnia umożliwia sprawdzenie w czasie kompilacji, czy istnieje sygnał / gniazdo, w którym stara składnia zawodzi w czasie wykonywania.
Vicky Chijwani
2
Niestety nieprzeciążanie sygnału często nie wchodzi w grę - Qt często przeciąża własne sygnały. (np. QSerialPort)
PythonNut
14

Komunikat o błędzie to:

błąd: brak funkcji pasującej do wywołania QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Ważną częścią tego jest wzmianka o „ nierozwiązanym typie przeciążonej funkcji ”. Kompilator nie wie, czy masz na myśli, QSpinBox::valueChanged(int)czy QSpinBox::valueChanged(QString).

Istnieje kilka sposobów rozwiązania tego problemu:

  • Podaj odpowiedni parametr szablonu do connect()

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

    Zmusza to connect()do rozwiązania problemu &QSpinBox::valueChangedprzeciążenia, które wymaga pliku int.

    Jeśli masz nierozwiązane przeciążenia dla argumentu gniazda, musisz podać drugi argument szablonu do connect(). Niestety, nie ma składni wymagającej wywnioskowania pierwszego, więc musisz podać oba. Wtedy może pomóc drugie podejście:

  • Użyj zmiennej tymczasowej odpowiedniego typu

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

    Przypisanie do signalwybierze żądane przeciążenie, a teraz można je z powodzeniem zastąpić w szablonie. Działa to równie dobrze z argumentem „szczelina” i uważam, że w tym przypadku jest to mniej kłopotliwe.

  • Użyj konwersji

    Możemy tego uniknąć static_cast, ponieważ jest to po prostu przymus, a nie usunięcie zabezpieczeń języka. Używam czegoś takiego:

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

    To pozwala nam pisać

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);
    
Toby Speight
źródło
8

Właściwie możesz po prostu owinąć swój slot lambda i to:

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

będzie wyglądać lepiej. : \

Newlifer
źródło
0

Powyższe rozwiązania działają, ale rozwiązałem to w nieco inny sposób, używając makra, więc na wszelki wypadek tutaj jest:

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

Dodaj to do swojego kodu.

Następnie Twój przykład:

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

Staje się:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);
Basile Perrenoud
źródło
2
Rozwiązania „ponad” co? Nie zakładaj, że odpowiedzi są prezentowane wszystkim w kolejności, w jakiej je aktualnie widzisz!
Toby Speight
1
Jak tego używać w przypadku przeciążeń, które wymagają więcej niż jednego argumentu? Czy przecinek nie powoduje problemów? Myślę, że naprawdę trzeba zdać pareny, czyli #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- używane jak CONNECTCAST(QSpinBox, valueChanged, (double))w tym przypadku.
Toby Speight
jest to przydatne makro, gdy nawiasy są używane dla wielu argumentów, jak w komentarzu Toby'ego
ejectamenta