Подключение перегруженных сигналов и слотов в Qt 5

133

У меня проблемы с освоением нового синтаксиса сигнала / слота (с использованием указателя на функцию-член) в 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.

Что я делаю не так, и как я могу это исправить?

dtruby
источник
Если ваш синтаксис правильный, то единственным объяснением может быть то, что вы связываетесь не с библиотеками Qt5, а, например, с Qt4. Это легко проверить с помощью QtCreator на странице «Проекты».
Мэтт Филлипс
Я включил несколько подклассов QObject (QSpinBox и т. Д.), Так что они должны были включать QObject. Я попытался добавить и этот include, но он все равно не компилируется.
dtruby
Кроме того, я определенно связываюсь с Qt 5, я использую Qt Creator, и два набора, которые я тестирую, содержат Qt 5.0.1, указанную в качестве их версии Qt.
dtruby

Ответы:

244

Проблема здесь в том, что есть два сигнала с таким именем: 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);

Я знаю, это некрасиво . Но выхода нет. Сегодняшний урок: не перегружайте свои сигналы и слоты!


Приложение : что действительно раздражает в актерском составе, так это то, что

  1. один повторяет название класса дважды
  2. нужно указать возвращаемое значение, даже если оно обычно void(для сигналов).

Итак, я обнаружил, что иногда использую этот фрагмент кода 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;
    } 
};

Использование:

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

Я лично считаю это не очень полезным. Я ожидаю, что эта проблема исчезнет сама собой, когда Creator (или ваша IDE) автоматически вставит правильное приведение при автозавершении операции взятия PMF. А пока ...

Примечание: синтаксис подключения на основе PMF не требует C ++ 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 говорит

Примечание. Signal valueChanged перегружен в этом классе. Чтобы подключиться к этому сигналу, используя синтаксис указателя функции, Qt предоставляет удобный помощник для получения указателя функции, как показано в этом примере:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
Peppe
источник
1
Ах да, в этом есть большой смысл. Я предполагаю, что в таких случаях, когда сигналы / слоты перегружены, я просто буду придерживаться старого синтаксиса :-). Спасибо!
dtruby
17
Я был так взволнован новым синтаксисом ... теперь холодная волна леденящего разочарования.
RushPL
12
Для тех, кому интересно (как и я): «pmf» означает «указатель на функцию-член».
Vicky Chijwani 02
14
Лично я предпочитаю static_castуродство старому синтаксису просто потому, что новый синтаксис позволяет проверять во время компиляции наличие сигнала / слота, когда старый синтаксис не работает во время выполнения.
Vicky Chijwani 02
2
К сожалению, не использовать перегрузку сигнала - часто Qt перегружает свои собственные сигналы. (например QSerialPort)
PythonNut 01
14

Сообщение об ошибке:

ошибка: нет соответствующей функции для вызова 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выберет желаемую перегрузку, и теперь ее можно успешно подставить в шаблон. Это одинаково хорошо работает с аргументом «слот», и в этом случае я считаю его менее громоздким.

  • Использовать преобразование

    Здесь можно избежать 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);
Тоби Спейт
источник
8

На самом деле, вы можете просто обернуть свой слот лямбдой и вот так:

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

будет лучше. : \

Newlifer
источник
0

Приведенные выше решения работают, но я решил это немного по-другому, используя макрос, так что на всякий случай вот он:

#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);
Базиль Перрену
источник
2
Решения "наверху" какие? Не думайте, что ответы представлены всем в том порядке, в котором вы их сейчас видите!
Тоби Спейт
1
Как вы используете это для перегрузок, которые принимают более одного аргумента? Не вызывает ли проблем запятая? Я думаю, вам действительно нужно передать паренсы, т.е. #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)использовать как CONNECTCAST(QSpinBox, valueChanged, (double))в данном случае.
Тоби Спейт
это хороший полезный макрос, когда скобки используются для нескольких аргументов, как в комментарии Тоби
ejectamenta