web-dev-qa-db-ja.com

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))の呼び出しに一致する関数がありません

Linuxでclangとgccを試してみました。どちらも-std=c++11を使用しました。

私は何を間違っていますか、どのように修正できますか?

113
dtruby

ここでの問題は、その名前(QSpinBox::valueChanged(int)QSpinBox::valueChanged(QString))にtwoシグナルがあるということです。 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);

私は知っている、それはugいです。しかし、これを回避する方法はありません。今日のレッスンは:信号とスロットを過負荷にしないでください!


補遺:キャストについて本当に迷惑なのは

  1. クラス名を2回繰り返す
  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), ...)

個人的にはあまり役に立たないと思います。 PMFを取得する操作をオートコンプリートするときに、Creator(またはIDE)が自動的に正しいキャストを挿入すると、この問題は自然に解消されると思います。しかし、その間に...

注:PMFベースの接続構文はC++ 11を必要としません!


補遺2:Qt 5.7では、これを緩和するためのヘルパー関数が追加され、上記の回避策をモデルにしています。メインヘルパーは qOverload です( qConstOverloadqNonConstOverload もあります)。

使用例(ドキュメントから):

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){ /* ... */ });
218
peppe

エラーメッセージは次のとおりです。

エラー: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::valueChangedintを取るオーバーロードに強制的に解決します。

    スロット引数に未解決のオーバーロードがある場合は、connect()に2番目のテンプレート引数を指定する必要があります。残念ながら、最初に推論するように要求する構文はないため、両方を指定する必要があります。そのとき、2番目のアプローチが役立ちます。

  • 正しいタイプの一時変数を使用する

    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);
    
12
Toby Speight

実際には、ラムダとこれでスロットをラップすることができます:

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

良くなります。 :\

8
Newlifer

上記の解決策は機能しますが、マクロを使用してこれをわずかに異なる方法で解決しました。

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