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ć?
Odpowiedzi:
Problem polega na tym, że istnieją dwa sygnały o tej nazwie:
QSpinBox::valueChanged(int)
iQSpinBox::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
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żqConstOverload
iqNonConstOverload
).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
źródło
static_cast
brzydotę 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.QSerialPort
)Komunikat o błędzie to:
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)
czyQSpinBox::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::valueChanged
przeciążenia, które wymaga plikuint
.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
signal
wybierze żą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);
źródło
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. : \
źródło
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:
Staje się:
QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged), slider, &QSlider::setValue);
źródło
#define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)
- używane jakCONNECTCAST(QSpinBox, valueChanged, (double))
w tym przypadku.