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

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

4 ответов


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

Я лично считаю, что это не очень полезно. Я ожидал проблема уйти сама по себе, когда Создатель (или ваша 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)

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

ошибка: нет подходящей функции для вызова 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);
    

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

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

будет выглядеть лучше. :\


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

#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);