Подключение перегруженных сигналов и слотов в 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);

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


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

  1. один повторяет имя класса дважды
  2. необходимо указать возвращаемое значение, даже если оно обычно 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);