web-dev-qa-db-ja.com

直接初期化と代入でラムダを渡すが、コピー初期化では渡さない場合、なぜこれがコンパイルされるのですか?

オブジェクトが宣言されているのと同じ行で代入演算子がラムダ式を許可しないのはなぜですか?

ただし、MSVCでは機能しているようです。

コードをテストします: https://godbolt.org/g/n2Tih1

class Func
{
    typedef void(*func_type)();
    func_type m_f;
public:
    Func() {}
    Func(func_type f) : m_f(f) {}
    Func operator=(func_type f) {
        m_f = f;
        return *this;
    }
};

int main()
{
    // doesn't compile in GCC and clang, it does in MSVC
    Func f1 = []() {

    };

    // compiles!
    Func f2;
    f2 = []() {

    };

    // compiles!
    Func f3([]() {

    });
}
23
tuket

Func f1 = []() {};コピーの初期化 であり、_f1_を構築するために2つのユーザー定義の暗黙的な変換が必要です。1つ目はラムダから関数ポインターへ、2つ目は関数ポインタからFuncへ。 1つの 変換シーケンス で許可されるユーザー定義の暗黙的な変換は1つだけなので、失敗します。

(私の強調)

Tがクラス型であり、otherの型のcv非修飾バージョンがTでないか、Tから派生している場合、またはTが非クラス型であるが、otherの型がクラス型である場合他の型からT(またはTがクラス型であり、変換関数が使用可能な場合はTから派生した型)に変換できるユーザー定義の変換シーケンスが調べられますそして、過負荷の解決によって最適なものが選択されます。

そして

暗黙的な変換シーケンスは、次の順序で構成されます。

1)ゼロまたは1つの標準変換シーケンス。
2)ゼロまたは1つのユーザー定義の変換。
3)ゼロまたは1つの標準変換シーケンス。

f2 = []() {};の場合、適切な代入演算子が呼び出されようとします。Funcには1つあり、引数として関数ポインターが必要です。ラムダから関数ポインターへの暗黙の変換は1つだけ必要であり、それでうまく機能します。

Func f3([]() {});直接初期化 であり、適切なコンストラクターが呼び出されようとします。Funcにはコンストラクターがあり、引数として関数ポインターが必要です。それなら_f2_と同じです。

コピーの初期化と直接の初期化の違いからポイントを得ることができます。

さらに、コピー初期化での暗黙的な変換では、初期化子から直接Tを生成する必要があります。直接初期化では、初期化子からTのコンストラクターの引数への暗黙的な変換が必要です。

22
songyuanyao

最初のケースでは、ラムダからvoid(*)()へ、次にvoid(*)()からFuncへの2つの暗黙的な変換が行われます。最大で1つの暗黙的な変換を行うことができます。

暗黙の変換の1つを削除できれば、正常に機能するはずです。ここにあなたが試すことができるいくつかの潜在的な解決策があります:

// Explicit cast to a function pointer
Func f1 = static_cast<void(*)()>([]() {});

// func_ptr is already a function pointer
//  eliminating one of the implcit conversions
void (*func_ptr)() = [](){};
Func f2 = func_ptr;

// The conversion from `void(*)()` is no longer implicit
Func f3{ [](){} };
8