copy constructor struct c++
¿El copy-constructor de std:: function requiere que los tipos de argumentos del tipo de plantilla sean tipos completos? (3)
Yo diría que no. Desde la función de plantilla de clase 20.8.11.2 [func.wrap.func], tenemos:
3 La plantilla de clase de
function
es un derivador de llamada (20.8.1) cuya firma de llamada (20.8.1) esR(ArgTypes...)
.
En 20.8.1 Definiciones [func.def], obtenemos las siguientes definiciones sobre qué constituye un tipo de derivador de llamadas, un derivador de llamadas y una firma de llamada:
2 Una firma de llamada es el nombre de un tipo de devolución seguido de una lista entre paréntesis separada por comas de cero o más tipos de argumentos.
5 Un tipo de envoltura de llamada es un tipo que contiene un objeto invocable y admite una operación de llamada que lo reenvía a ese objeto.
6 Un contenedor de llamadas es un objeto del tipo de contenedor de llamadas.
Observe cómo el párrafo 2 no menciona la integridad de los tipos.
Para abreviar una historia (hay muchas definiciones involucradas), el significado de ''objeto invocable'' aquí significa un functor (la noción familiar, es decir, algo que puede usarse como una función) o un puntero a miembro. Además, el estándar también describe el concepto de llamada en 20.8.11.2 párrafo 2:
Un objeto invocable
f
de tipoF
es invocable para tipos de argumentosArgTypes
y devuelve tipoR
si la expresión INVOKE(f, declval<ArgTypes>()..., R)
, considerada como un operando no evaluado (cláusula 5), está bien formada (20.8.2).
(El bit INVOKE es una función imaginaria que el estándar usa para definir cómo se invocan, y así se invocan, los funtores y los punteros a los miembros).
Lo que creo que es la conclusión más importante de eso es el siguiente requisito:
- dado un objeto invocable que es invocable con la firma
R(A...)
, entoncesR
yA...
están completos (o posiblementeR
esvoid
) en virtud de la expresión INVOKE (es decir, de lo contrario no estaría bien formado, observe el uso dedeclval<ArgTypes>()...
)
Mi argumento ahora descansa en ''[la] operación de llamada que reenvía a ese objeto'' en la definición de wrappers de llamadas, que creo que se dejó intencionalmente vago para no ser demasiado restrictivo. En el caso de std::function<Sig>
donde algunos tipos incompletos están involucrados en Sig
entonces podríamos definir esta operación como ''primero complete los tipos, luego trate std::function<Sig>
como un tipo de objeto de llamamiento de firma de llamada Sig
''.
Dado que, aquí están los puntos clave de mi argumento:
-
std::function
no se describe como un objeto invocable o como invocable para cualquier firma - La invocación de una
std::function
se realiza en términos de INVOKE (invocación de función 20.8.11.2.4 [func.wrap.func.inv]) - construir una
std::function
desde un objeto invocable es en términos de invocable con la firma de llamada destd::function
(20.8.11.2.1 función construir / copiar / destruir [func.wrap.func.con] párrafo 7) - llamar al miembro de
target
destd::function
está en términos de invocable con la firma de llamada destd::function
(20.8.11.2.5 función de acceso a destino [func.wrap.func.targ]) - todas las demás operaciones de
std::function
no se describen en términos de objeto invocable (*), invocable , INVOKE o de otro modo requieren que la firma de llamada destd::function
involucre tipos completos
(*) excepto en el caso de un constructor en el que la descripción contenga "no arrojará excepciones si el objetivo de f
es un objeto invocable pasado a través de reference_wrapper
o un puntero de función". Creo que en el contexto está claro que esto no afecta el argumento. Por lo que vale, este constructor no está involucrado en el fragmento del PO.
Entonces, a menos que utilices una de esas operaciones que requieren indirectamente que la firma involucre tipos completos, diría que estás listo para empezar.
Está muy bien analizar lo que prescribe el Estándar, pero también es importante considerar cuál es la intención del Estándar. En este caso, creo que es muy deseable y esperado que std::function
no requiera que los tipos de la firma de llamada estén completos. Considera lo siguiente:
// in a_fwd.hpp
struct incomplete;
using callback_type = std::function<void(incomplete)>;
callback_type make_callback();
// in b.hpp; depends on a_fwd.hpp
#include "a_fwd.hpp"
void eat_callback(callback_type);
Entonces, sin el requisito de una TU no relacionada, llamémosla C, eso es lo que un cliente de B puede hacer:
// in c.cpp
#include "b.hpp"
// somewhere in e.g. a function body
eat_callback(make_callback());
Esto es seguro y minimiza el acoplamiento ya que solo la unidad de traducción B necesita conocer los detalles de la unidad de traducción A.
Además, tanto Boost.Function como libstdc ++ han demostrado que es posible implementar la std::function
sin ese requisito.
Dado:
#include <functional>
class world_building_gun;
class tile_bounding_box;
typedef std::function<void (world_building_gun, tile_bounding_box)> worldgen_function_t;
void foo() {
worldgen_function_t v;
worldgen_function_t w(v);
}
¿Debería esto compilar? Mis compiladores dicen:
Sí: GCC / stdlibc ++ (también boost :: la función es sí tanto en GCC como en Clang)
No: Clang / libc ++ ( http://libcxx.llvm.org/ , Clang 3.0, SVN de libc ++ a partir de hoy)
(Si "no" es la respuesta correcta, corregiré mi código real para poner tipos completos en más encabezados o usar la función boost ::).
EDITAR: Aquí está el mensaje de error Clang:
In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid appli
static_assert(sizeof(_Tp) > 0, "Type must be complete.");
^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2752:15: note: in instantiation of template class ''std::__1::__check_complete<world_buildin
: private __check_complete<_Hp>,
^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class ''std::__1::__check_complete<world_buildin
private __check_complete<_T0, _Tp...>
^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class ''std::__1::__check_complete<std::__1::fun
world_building_gun, tile_bounding_box>'' requested here
: private __check_complete<_Fp, _Args...>
^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class ''std::__1::__invokable_imp<std::__1::func
world_building_gun, tile_bounding_box>'' requested here
__invokable_imp<_Fp, _Args...>::value>
^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class ''std::__1::__invokable<std::__1::function<
world_building_gun, tile_bounding_box>'' requested here
template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for ''__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >'' required here
typename enable_if<__callable<_Fp>::value>::type* = 0);
^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template ''function'' [with _Fp = std::__1::function<void
(world_building_gun, tile_bounding_box)>]
function(_Fp,
^
foo.cpp:4:7: note: forward declaration of ''world_building_gun''
class world_building_gun;
^
In file included from foo.cpp:2:
In file included from /usr/include/c++/v1/functional:462:
/usr/include/c++/v1/type_traits:2766:19: error: invalid application of ''sizeof'' to an incomplete type ''tile_bounding_box''
static_assert(sizeof(_Tp) > 0, "Type must be complete.");
^~~~~~~~~~~
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class ''std::__1::__check_complete<tile_bounding_box>'' requested here
private __check_complete<_T0, _Tp...>
^
/usr/include/c++/v1/type_traits:2753:15: note: in instantiation of template class ''std::__1::__check_complete<world_building_gun, tile_bounding_box>'' requested here
private __check_complete<_T0, _Tp...>
^
/usr/include/c++/v1/type_traits:2919:15: note: in instantiation of template class ''std::__1::__check_complete<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
world_building_gun, tile_bounding_box>'' requested here
: private __check_complete<_Fp, _Args...>
^
/usr/include/c++/v1/type_traits:2930:11: note: in instantiation of template class ''std::__1::__invokable_imp<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
world_building_gun, tile_bounding_box>'' requested here
__invokable_imp<_Fp, _Args...>::value>
^
/usr/include/c++/v1/functional:1115:33: note: in instantiation of template class ''std::__1::__invokable<std::__1::function<void (world_building_gun, tile_bounding_box)> &,
world_building_gun, tile_bounding_box>'' requested here
template <class _Fp, bool = __invokable<_Fp&, _ArgTypes...>::value>
^
/usr/include/c++/v1/functional:1141:35: note: in instantiation of default argument for ''__callable<std::__1::function<void (world_building_gun, tile_bounding_box)> >'' required here
typename enable_if<__callable<_Fp>::value>::type* = 0);
^~~~~~~~~~~~~~~
/usr/include/c++/v1/functional:1140:7: note: while substituting deduced template arguments into function template ''function'' [with _Fp = std::__1::function<void
(world_building_gun, tile_bounding_box)>]
function(_Fp,
^
foo.cpp:5:7: note: forward declaration of ''tile_bounding_box''
class tile_bounding_box;
^
2 errors generated.
Clang + libc ++ se compila correctamente si elimino la línea "worldgen_function_t w (v);" o si hago las clases tipos completos.
Apliqué una corrección a libc ++ para que este ejemplo compilara ahora la revisión comprometida 160285.
Creo que libc ++ era demasiado agresivo al verificar la lista de argumentos para tipos completos.
Editar: Aparentemente , este problema ahora está solucionado, por lo que el texto a continuación se puede ver como historial. :)
El problema es de hecho (como predije) con las comprobaciones SFINAE de libc ++ en el ctor con plantilla (por un razonamiento, verifique esta pregunta ). Comprueba si lo siguiente (por ejemplo) es válido y da un error agradable y limpio en el sitio de construcción en lugar de en las entrañas de std::function
(prueba el siguiente ejemplo con libstd ++ o MSVC ... estremecimiento ):
#include <functional>
void f(int* p){}
int main(){
std::function<void(int)> fun(f);
}
libc ++ causará que el compilador escuche algo en la línea de "no se encontró ningún constructor que coincida con la lista de argumentos void (*)(int*)
", ya que el único aplicable (el ctor con plantilla) se saca SFINAE.
Sin embargo, para que las __callable
y __invoke_imp
funcionen, los tipos de argumento y de retorno deben estar completos, ya que de lo contrario las conversiones implícitas no se tendrán en cuenta aquí.
La razón por la cual el ctor con plantilla incluso se mira es que todos los ctors se enumeran antes de considerar una mejor coincidencia (en este caso, el ctor de copia).
Ahora, el estándar es muy claro que el argumento y los tipos de retorno deben estar completos cuando se construye un objeto std::function
partir de un objeto invocable (también conocido como call the templated ctor):
§20.8.11.2.1 [func.wrap.func.con] p7
template <class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
Requiere:
F
debe serCopyConstructible
.f
debe ser invocable (20.8.11.2) para tipos de argumentosArgTypes
y return typeR
[...]
(Nota: "Requiere" se dirige al usuario de la funcionalidad, no al implementador).
§20.8.11.2 [func.wrap.func] p2
Un objeto invocable
f
de tipoF
es invocable para tipos de argumentosArgTypes
y devuelve tipoR
si la expresiónINVOKE
(f, declval<ArgTypes>()..., R)
, considerada como un operando no evaluado (cláusula 5), está bien formada (20.8.2).
§20.8.2 [func.req]
p1 Defina
INVOKE
(f, t1, t2, ..., tN)
siguiente manera:
(t1.*f)(t2, ..., tN)
cuandof
es un puntero a una función miembro de una claseT
yt1
es un objeto de tipoT
o una referencia a un objeto de tipoT
o una referencia a un objeto de un tipo derivado deT
;((*t1).*f)(t2, ..., tN)
cuandof
es un puntero a una función miembro de una claseT
yt1
no es uno de los tipos descritos en el elemento anterior;- [...]
f(t1, t2, ..., tN)
en todos los demás casos.p2 Defina
INVOKE
(f, t1, t2, ..., tN, R)
comoINVOKE
(f, t1, t2, ..., tN)
convertido implícitamente aR
Por lo tanto, libc ++ está ciertamente dentro de sus derechos para hacer la verificación SFINAE en el ctor con plantilla, ya que los tipos deben estar completos, ya que de lo contrario obtendría un comportamiento indefinido. Sin embargo, puede ser un poco desafortunado y se considerará un defecto que la verificación de seguridad para un tipo completo se active incluso si la verificación SFINAE real nunca se necesita (porque siempre se invocará el copiador). Esto se puede aliviar haciendo que el cheque callable
flojo, como
template<bool Copy, class F>
struct lazy_callable{
static bool const value = callable<F>::value;
};
template<class F>
struct lazy_callable<true, F>{
static bool const value = false;
};
template<class F>
function(F f, typename enable_if<lazy_callable<!std::is_same<F,function>::value>::type* = 0);
Esto solo debería activar la comprobación de SFINAE callable
si F
no es realmente std::function<...>
.
Hombre, puede que haya desviado un poco aquí al final ...