Подключение перегруженных сигналов и слотов в Qt 5
У меня возникли проблемы с получением нового синтаксиса сигнала/слота (с использованием указателя на функцию-член) в Qt 5, как описано в Новый слот сигнала Синтаксис. Я попытался изменить это:
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int));
:
QObject::connect(spinBox, &QSpinBox::valueChanged,
slider, &QSlider::setValue);
но я получаю сообщение об ошибке при попытке скомпилировать его:
ошибка: нет соответствующей функции для вызова QObject::connect(QSpinBox*&,
<unresolved overloaded function type>, QSlider*&, void
(QAbstractSlider::*)(int))
Я пробовал с clang и gcc в Linux, как с -std=c++11
.
Что я делаю неправильно, и как я могу это исправить?
Ответы
Ответ 1
Проблема здесь в том, что есть два сигнала с таким именем: QSpinBox::valueChanged(int)
и QSpinBox::valueChanged(QString)
. В Qt 5.7 предусмотрены вспомогательные функции для выбора желаемой перегрузки, поэтому вы можете написать
connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
slider, &QSlider::setValue);
В Qt 5.6 и более ранних версиях вам нужно указать Qt, какой из них вы хотите выбрать, приведя его к нужному типу:
connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
slider, &QSlider::setValue);
Я знаю, это уродливо. Но нет никакого способа обойти это. Сегодняшний урок: не перегружайте свои сигналы и слоты!
Приложение: что действительно раздражает в актерском составе, так это то, что
- один повторяет имя класса дважды
- необходимо указать возвращаемое значение, даже если оно обычно
void
(для сигналов).
Поэтому я иногда использую этот фрагмент кода С++ 11:
template<typename... Args> struct SELECT {
template<typename C, typename R>
static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) {
return pmf;
}
};
Использование:
connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)
Я лично нахожу это не очень полезным. Я ожидаю, что эта проблема исчезнет сама собой, когда Creator (или ваша IDE) автоматически вставит правильное приведение при автоматическом завершении операции получения PMF. Но в то же время...
Примечание. Синтаксис соединения на основе PMF не требует С++ 11!
Приложение 2: в Qt 5.7 были добавлены вспомогательные функции для смягчения этого, смоделированные после моего обходного пути выше. Основным помощником является qOverload
(у вас также есть qConstOverload
и qNonConstOverload
).
Пример использования (из документов):
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)
Приложение 3: если вы посмотрите на документацию по любому перегруженному сигналу, то теперь решение проблемы перегрузки четко указано в самих документах. Например, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 говорит
Примечание. Сигнал valueChanged перегружен в этом классе. Чтобы подключиться к этому сигналу с использованием синтаксиса указателя функции, Qt предоставляет удобный помощник для получения указателя функции, как показано в этом примере:
connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
Ответ 2
Сообщение об ошибке:
ошибка: нет подходящей функции для вызова QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))
Важной частью этого является упоминание "неразрешенного типа перегруженной функции". Компилятор не знает, имеете ли вы в виду QSpinBox::valueChanged(int)
или QSpinBox::valueChanged(QString)
.
Существует несколько способов устранения перегрузки:
Укажите подходящий параметр шаблона для connect()
QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
slider, &QSlider::setValue);
Это заставляет connect()
преобразовать &QSpinBox::valueChanged
в перегрузку, которая принимает int
.
Если у вас есть неразрешенные перегрузки для аргумента слота, вам нужно будет передать второй аргумент шаблона в connect()
. К сожалению, нет синтаксиса, чтобы запрашивать первый вывод, поэтому вам нужно предоставить оба. Это когда второй подход может помочь:
Используйте временную переменную правильного типа
void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
QObject::connect(spinBox, signal,
slider, &QSlider::setValue);
Присвоение signal
выберет желаемую перегрузку, и теперь она может быть успешно подставлена в шаблон. Это одинаково хорошо работает с аргументом 'slot', и в этом случае я считаю его менее громоздким.
Использовать конверсию
Мы можем избежать static_cast
здесь, так как это просто принуждение, а не снятие языковой защиты. Я использую что-то вроде:
// Also useful for making the second and
// third arguments of ?: operator agree.
template<typename T, typename U> T&& coerce(U&& u) { return u; }
Это позволяет нам писать
QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
slider, &QSlider::setValue);
Ответ 3
Собственно, вы можете просто обернуть свой слот лямбдой и это:
connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
slider, &QSlider::setValue);
будет выглядеть лучше.:\
Ответ 4
Решения выше работают, но я решил это несколько иначе, используя макрос. Так что на всякий случай здесь:
#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)
Добавьте это в свой код.
Затем ваш пример:
QObject::connect(spinBox, &QSpinBox::valueChanged,
slider, &QSlider::setValue);
становится:
QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
slider, &QSlider::setValue);