Почему это компилируется при передаче лямбда в прямой инициализации и присваивании, но не с инициализацией копии?

Почему оператор присваивания не допускает выражение лямбда, когда оно сделано в той же строке, что и объект объявлен?

Однако, похоже, он работает в 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([]() {

    });
}

Ответ 1

Func f1 = []() {}; это инициализация копирования, для которой требуется два определяемых пользователем неявного преобразования для построения f1, первый из них - от лямбда до указателя функции, второй - от указателя функции до Func. Только одно пользовательское неявное преобразование допускается в одной последовательности преобразований, поэтому оно терпит неудачу.

(акцент мой)

Если T - тип класса, а cv-неквалифицированная версия типа другого не является T или получена из T, или если T является неклассовым типом, а тип другого - тип класса, пользовательские последовательности преобразования который может преобразовывать из другого типа в T (или в тип, полученный из T, если T является типом класса и доступна функция преобразования), и лучший выбирается с помощью разрешения перегрузки.

а также

Неявная последовательность преобразования состоит из следующего, в следующем порядке:

1) нулевая или одна стандартная последовательность преобразования;
2) нулевое или одно пользовательское преобразование;
3) нулевая или одна стандартная последовательность преобразования.

Для f2 = []() {}; вызывается соответствующий оператор присваивания, Func имеет одно и ожидает, что указатель функции будет аргументом; требуется только одно неявное преобразование из лямбда в указатель функции, а затем оно работает хорошо.

Func f3([]() {}); является прямой инициализацией, соответствующий конструктор пытается быть вызванным, Func имеет один и ожидает, что указатель функции будет аргументом. Тогда это то же самое, что и f2.

Вы можете понять суть различия между инициализацией и прямой инициализацией.

Кроме того, неявное преобразование в инициализации копии должно производить T непосредственно из инициализатора, тогда как, например, прямая инициализация ожидает неявного преобразования из инициализатора в аргумент конструктора T.

Ответ 2

Ваш первый случай включает в себя два неявных преобразования, lambda to void(*)() затем void(*)() в Func. У вас может быть не более 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{ [](){} };