c++ - pick - ¿Por qué la asignación a std:: function<X()> no se compila cuando es un miembro de la clase X?
mercedes clase x 350 (2)
El siguiente código no compila:
#include <functional>
struct X
{
std::function<X()> _gen;
};
int main()
{
X x;
x._gen = [] { return X(); }; //this line is causing problem!
}
No entiendo por qué la asignación a x._gen
está causando problemas. Tanto gcc como clang están dando mensajes de error similares. ¿Alguien podría por favor explicarlo?
Mensajes de error del compilador
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
Asimismo, clang esto:
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
^
Esto fue PR60594 , que se corrigió en GCC 4.8.3. Los comentarios sobre ese error señalan por qué es válido: aunque el estándar requiere argumentos de plantilla para que las plantillas de biblioteca estándar sean un tipo completo (con algunas excepciones), X()
es un tipo completo, incluso si X
no lo es.
Hay varios miembros de std::function<X()>
que requieren implícitamente que X
sea un tipo completo. El constructor de plantillas que está usando es uno de ellos: requiere que el tipo de retorno de su lambda sea convertible implícitamente a X
, pero si X
es convertible a sí mismo depende de si X
es un tipo completo: si está incompleto, el compilador puede No descarte la posibilidad de que sea un tipo inamovible que no se pueda copiar.
Este requisito se deduce de:
20.9.11.2.1 función construir / copiar / destruir [func.wrap.func.con]
8 Observaciones: Estos constructores no participarán en la resolución de sobrecarga a menos que
f
sea Callable (20.9.11.2) para los tipos de argumentoArgTypes...
y devuelva el tipoR
20.9.11.2 Función de plantilla de clase [func.wrap.func]
2 Un objeto llamable
f
de tipoF
es Callable para los tipos de argumentoArgTypes
y devuelve el tipoR
si la expresiónINVOKE
(f, declval<ArgTypes>()..., R)
, considerada como un operando no evaluado (Cláusula 5), está bien formado (20.9.2).20.9.2 Requisitos [func.require]
2 Defina
INVOKE
(f, t1, t2, ..., tN, R)
comoINVOKE
(f, t1, t2, ..., tN)
convertido implícitamente aR
Varios otros miembros de std::function
también requieren que X
sea un tipo completo.
Sin embargo, solo estás usando ese constructor después de que el tipo X
ya se haya completado, así que no hay problema: en ese punto, X
puede convertirse implícitamente en X
El problema era que la std::function
realizaba verificaciones que dependían de que X
fuera un tipo completo, en un contexto en el que el estándar no admite la realización de tales verificaciones, y esto no tenía en cuenta la posibilidad de que X
se convirtiera en un tipo completo después de la instanciación de std::function<X()>
ya se había completado.
Esto puede ser un error de gcc, pero tal vez no. No está directamente en =
sino en el constructor de conversión para std::function
(que el operator=
invoca).
Aquí hay un ejemplo patológico de lo que está pasando:
#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!
}
tenga en cuenta que el primer foo1
funciona bien, no es hasta que hago que algún código invoque el ctor de copia en el que el segundo genera errores. Incluso auto unused =[]{ return X{}; };
auto unused =[]{ return X{}; };
es suficiente. (construcciones de func
directas y nunca copias).
Es el uso / "creación" del ctor de copia lo que parece causar el problema.
#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
}
ese constructor de copias termina llamando al ctor de copia de _gen
, posiblemente antes de que X
sea un tipo completo.
Si demoramos explícitamente la creación de instancias de X::X(X const&)
hasta que X
sea un tipo completo:
#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!
}
el problema desaparece
Sospecho que el constructor de copia implícita de X
creado en el cuerpo de X
cuando X
es un tipo incompleto invoca implícitamente el constructor de copia de std::function<X()>
, que está en un contexto donde X
está incompleto, lo que rompe el Las condiciones previas a la invocación de su constructor de copia (al menos en la práctica sobre cómo se implementó en gcc - por la norma? No estoy seguro).
Al hacer explícitamente una copia fuera de X
, evito esto y todo funciona.
Así que para solucionar su problema, declare e implemente X::X(X const&)
fuera de X
, y el error mágico desaparece.