5

このプログラムをgcc-4.6.3またはgcc-4.7.2のいずれかでコンパイルすると、コンパイラーは、オーバーロードされた呼び出しがあいまいであるというエラーを表示します。

#include <iostream>
#include <functional>

class Scott
{
    public:
        void func(const bool b = true)
        {
            std::cout << "Called func() with a boolean arg" << std::endl;
        }

        void func(std::function<void(void)> f)
#ifdef WITH_CONST
            const
#endif
        {
            std::cout << "Called func() with a std::function arg" << std::endl;
        }
};


int main (int argc, char *argv[])
{
    Scott s;
    s.func([] (void) { });
}

ただし、オーバーロードされた関数をconstにすると、正常にコンパイルされ、予期しないメソッドが呼び出されます。

devaus120>> g++ -Wall -std=c++11 -DWITH_CONST wtf.cxx
devaus120>> ./a.out
Called func() with a boolean arg

だから、私は2つの質問があります:

  1. オーバーロードされたメソッドがconstになったときにこれがコンパイルされるのはコンパイラのバグですか?
  2. 正しいオーバーロードされた関数が呼び出されるようにするにはどうすればよいですか?(どういうわけか議論を投げかける必要がありますか?)

TIA。

スコット。:)

4

1 に答える 1

4

実際、gccは正しいです!ラムダは関数ではなく、クラス型のクロージャオブジェクトだからです!本当に!あなたはそれから継承することさえできます:)...異なるラムダから何度も..。

したがって、8.5 / 16によると:


[...]

—宛先タイプが(おそらくcv修飾された)クラスタイプの場合:

[...]

それ以外の場合、ソースタイプが(おそらくcv修飾された)クラスタイプである場合、変換関数が考慮されます。該当する変換関数が列挙され(13.3.1.5)、過負荷解決(13.3)によって最適なものが選択されます。そのように選択されたユーザー定義の変換は、初期化式を初期化されるオブジェクトに変換するために呼び出されます。変換を実行できないか、あいまいな場合、初期化の形式が正しくありません。


および13.3.1.5:


8.5で指定された条件の下で、非クラス型のオブジェクトの初期化の一部として、変換関数を呼び出して、クラス型のイニシャライザ式を初期化されるオブジェクトの型に変換できます。過負荷解決は、呼び出す変換関数を選択するために使用されます。「cv1T」が初期化されるオブジェクトの型であり、「cv S」が初期化式の型であり、Sがクラス型であると仮定すると、候補関数は次のように選択されます。

--Sとその基本クラスの変換関数が考慮されます。S内に隠されておらず、タイプTまたは標準の変換シーケンス(13.3.3.1.1)を介してタイプTに変換できるタイプを生成する非明示的な変換関数が候補関数です。直接初期化の場合、S内に隠されておらず、タイプTまたは資格変換(4.4)でタイプTに変換できるタイプを生成する明示的な変換関数も候補関数です。cv修飾型を返す変換関数は、候補関数を選択するこのプロセスで、その型のcv非修飾バージョンを生成すると見なされます。「cv2Xへの参照」を返す変換関数は、参照のタイプに応じて、タイプ「cv2 X」の左辺値またはx値を返すため、候補関数を選択するこのプロセスでXを生成すると見なされます。


boolしたがって、最後に、変換関数の結果は、暗黙的に...に変換される関数ポインターになります。

この一連の変換は、次の簡単なコードで確認できます。

#include <iostream>
#include <iomanip>

int main()
{
    std::cout << std::boolalpha << []{ return 0; } << '\n';
}

出力はtrue...

回避策はいくつかあります...どちらの関数も過負荷解決後に適しているため、必ず何かが必要です。ところで、2番目の署名にを追加するconstと、の変更可能なインスタンスがあるため、それを除外するだけです。また、修飾子Scottを使用して宣言すると、コンパイルエラーが発生します。const

だから、あなたはすることができます:

  • 明示的なキャスト(コメントで言及されているように)...ええ、入力するのに長いです...
  • テンプレートパラメータを使用して2番目のfooを宣言しますFunc。次に何をするかに応じて、いくつかのオプションがあります。std::function割り当て時に変換することができます(メンバーに保存する場合)。または、すぐに呼び出す場合は、最適化を行うこともできます(削除することにより)。std::function)への変換
  • より複雑な方法は、テンプレートパラメータを使用して両方の関数を宣言し、たとえば、それに応じてstd::enable_if一方をオフにするために使用することです(または呼び出し可能/関数型を確認します)std::is_same<bool, T>
  • タイプディスパッチを使用します(ええ、テンプレート関数を使用します)

...私はそれで十分だと思います:)

于 2012-12-06T02:22:49.887 に答える