Почему назначение в 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
, и волшебная ошибка исчезнет.