Почему назначение в std:: function <X()> не компилируется, когда оно является членом класса X?

Следующий код не компилируется:

#include <functional>

struct X
{
    std::function<X()> _gen;
};

int main()
{
    X x;
    x._gen = [] { return X(); }; //this line is causing problem!
}

Я не понимаю, почему назначение x._gen вызывает проблему. Оба gcc и clang дают похожие сообщения об ошибках. Может ли кто-нибудь объяснить это?


Сообщения об ошибках компилятора

Ошибка GCC:

In file included from main.cpp:1:0:
/usr/include/c++/4.8/functional: In instantiation of ‘std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_CheckResult<std::function<_Res(_ArgTypes ...)>::_Invoke<_Functor>, _Res>, std::function<_Res(_ArgTypes ...)>&> std::function<_Res(_ArgTypes ...)>::operator=(_Functor&&) [with _Functor = main()::__lambda0; _Res = X; _ArgTypes = {}; std::function<_Res(_ArgTypes ...)>::_Requires<std::function<_Res(_ArgTypes ...)>::_CheckResult<std::function<_Res(_ArgTypes ...)>::_Invoke<_Functor>, _Res>, std::function<_Res(_ArgTypes ...)>&> = std::function<X()>&]’:
main.cpp:11:12:   required from here
/usr/include/c++/4.8/functional:2333:4: error: no matching function for call to ‘std::function<X()>::function(main()::__lambda0)’
    function(std::forward<_Functor>(__f)).swap(*this);
    ^
/usr/include/c++/4.8/functional:2333:4: note: candidates are:
/usr/include/c++/4.8/functional:2255:2: note: template<class _Functor, class> std::function<_Res(_ArgTypes ...)>::function(_Functor)
  function(_Functor);
  ^
/usr/include/c++/4.8/functional:2255:2: note:   template argument deduction/substitution failed:
/usr/include/c++/4.8/functional:2230:7: note: std::function<_Res(_ArgTypes ...)>::function(std::function<_Res(_ArgTypes ...)>&&) [with _Res = X; _ArgTypes = {}]
       function(function&& __x) : _Function_base()
       ^
/usr/include/c++/4.8/functional:2230:7: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘std::function<X()>&&’
/usr/include/c++/4.8/functional:2433:5: note: std::function<_Res(_ArgTypes ...)>::function(const std::function<_Res(_ArgTypes ...)>&) [with _Res = X; _ArgTypes = {}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/include/c++/4.8/functional:2433:5: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘const std::function<X()>&’
/usr/include/c++/4.8/functional:2210:7: note: std::function<_Res(_ArgTypes ...)>::function(std::nullptr_t) [with _Res = X; _ArgTypes = {}; std::nullptr_t = std::nullptr_t]
       function(nullptr_t) noexcept
       ^
/usr/include/c++/4.8/functional:2210:7: note:   no known conversion for argument 1 from ‘main()::__lambda0’ to ‘std::nullptr_t’
/usr/include/c++/4.8/functional:2203:7: note: std::function<_Res(_ArgTypes ...)>::function() [with _Res = X; _ArgTypes = {}]
       function() noexcept
       ^
/usr/include/c++/4.8/functional:2203:7: note:   candidate expects 0 arguments, 1 provided

Аналогично, Clang выбрасывает это:

main.cpp:11:12: error: no viable overloaded '='
    x._gen = [] { return X(); };
    ~~~~~~ ^ ~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2270:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'const std::function<X ()>' for 1st argument
      operator=(const function& __x)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2288:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'std::function<X ()>' for 1st argument
      operator=(function&& __x)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2302:7: note: candidate function not viable: no known conversion from '<lambda at main.cpp:11:14>' to 'nullptr_t' for 1st argument
      operator=(nullptr_t)
      ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2192:39: note: candidate template ignored: disabled by 'enable_if' [with _Functor = <lambda at main.cpp:11:14>]
        using _Requires = typename enable_if<_Cond::value, _Tp>::type;
                                             ^
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2340:2: note: candidate template ignored: could not match 'reference_wrapper<type-parameter-0-0>' against '<lambda at main.cpp:11:14>'
        operator=(reference_wrapper<_Functor> __f) noexcept
        ^

Ответы

Ответ 1

Это было PR60594, которое было исправлено в GCC 4.8.3. В комментариях к этой ошибке указывается, почему она действительна: хотя для стандартного шаблона шаблоны шаблонов шаблонов стандартных типов (за некоторыми исключениями), X() является полным типом, даже если X не является.

Есть несколько членов std::function<X()>, которые неявно требуют, чтобы X был полным типом. Конструктор шаблонов, который вы используете, является одним из них: он требует, чтобы возвращаемый тип вашей лямбда был неявно конвертируемым в X, но независимо от того, является ли X самозависимым, зависит от того, является ли X полным типом: if он неполный, компилятор не может исключить возможность того, что он является неподъемным неподвижным типом.

Это требование следует из:

20.9.11.2.1 function construct/copy/destroy [func.wrap.func.con]

8 Замечания: Эти конструкторы не должны участвовать в разрешении перегрузки, если f не является вызываемым (20.9.11.2) для типов аргументов ArgTypes... и типа возврата R.

20.9.11.2 Функция шаблона класса [func.wrap.func]

2 Вызываемый объект f типа f доступен для типов аргументов ArgTypes и возвращает тип R, если выражение INVOKE (f, declval<ArgTypes>()..., R), рассматриваемое как неоцениваемый операнд (п. 5), является (20.9.2).

20.9.2 Требования [func.require]

2 Определите INVOKE (f, t1, t2, ..., tN, R) как INVOKE (f, t1, t2, ..., tN) неявно преобразованный в R.

Несколько других членов std::function также требуют, чтобы X был полным типом.

Вы используете этот конструктор только после того, как тип X уже завершен, поэтому нет проблем: в этой точке X, безусловно, может быть неявно преобразован в X.

Проблема заключалась в том, что std::function выполнял проверки, которые зависят от X, как полный тип, в контексте, когда стандарт не поддерживает выполнение таких проверок, и это не учитывает возможность того, что X станет полным типом после того, как экземпляр std::function<X()> уже завершен.

Ответ 2

Это может быть ошибка gcc, но, возможно, нет. Он не находится непосредственно в =, а скорее в конструкторе преобразования для std::function (который вызывает operator=).

Вот патологический пример этого:

#include <iostream>
#include <functional>

struct X
{
  std::function<X()> _gen;
};

X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // compiles
  X unused = X{}; // copy ctor invoked
  std::function<X()> foo2( &func ); // does not compile!
}

обратите внимание, что первый foo1 работает отлично, только когда я вызываю какой-то код где-нибудь, вызывается копия ctor, вторая генерирует ошибки. Даже auto unused =[]{ return X{}; }; достаточно. (func прямые конструкции и никогда не копирует).

Это использование/ "создание" копии ctor, которая, кажется, вызывает проблему.

#include <iostream>
#include <functional>

struct X
{
  std::function<X()> _gen;
  X( X const& ) = default;
  X() = default;
};
X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // does not compile
}

этот конструктор копирования заканчивается вызовом копии ctor _gen, возможно, до X является полным типом.

Если мы явно задерживаем создание экземпляра X::X(X const&), пока X не будет полным типом:

#include <functional>

struct X
{
  std::function<X()> _gen;
  X( X const& );
  X() {}
};
X::X( X const& o ):_gen(o._gen){} // or =default *here*

X func() {return {};};

int main()
{
  std::function<X()> foo1( &func ); // compiles!
  []{ return X{}; }; // or X unused = X{};
  std::function<X()> foo2( &func ); // compiles!
}

проблема исчезает.

Я подозреваю, что неявный конструктор копирования X, созданный в теле X, когда X является неполным типом, неявно вызывает конструктор копирования std::function<X()>, который находится в контексте, где X является неполным, который прерывает предпосылки его конструктора копирования, вызываемого (по крайней мере, на практике в том, как он был реализован в gcc - стандартом? Я не уверен.)

Явным образом сделав копию ctor вне X, я избегаю этого, и все работает.

Итак, работайте над своей проблемой, объявите и внесите X::X(X const&) вне X, и волшебная ошибка исчезнет.