Considera este ejemplo de código:
#include #include typedef std::function func1_t; typedef std::function func2_t; struct X { X (func1_t f) { } X (func2_t f) { } }; int main ( ) { X x([](){ std::cout << "Hello, world!\n"; }); }
Estaba seguro de que no debería comstackrse, porque el comstackdor no debería poder elegir uno de los dos constructores. g ++ – 4.7.3 muestra este comportamiento esperado: dice que la llamada del constructor sobrecargado es ambigua. Sin embargo, g ++ – 4.8.2 lo comstack con éxito.
¿Este código es correcto en C ++ 11 o es un error / característica de esta versión de g ++?
En C ++ 11 …
Echemos un vistazo a la especificación de la plantilla de constructor de std::function
(que toma cualquier invocable): [func.wrap.func.con] / 7-10
template
function(F f); template function(allocator_arg_t, const A& a, F f); 7 Requiere:
F
debe serCopyConstructible
.f
debe serCallable
(20.10.11.2) para tipos de argumentosArgTypes
y tipo de retornoR
El constructor de copia y el destructor deA
no lanzarán excepciones.8 Postcondiciones:!
!*this
si se cumple cualquiera de los siguientes:
f
es un puntero de funciónNULL
.f
es un punteroNULL
al miembro.F
es una instancia de la plantilla de clase de función, y!f
9 De lo contrario,
*this
dirige a una copia def
inicializada constd::move(f)
. [dejó fuera una nota aquí]10 Lanzamientos: no lanzará excepciones cuando
f
sea un puntero a la función o unreference_wrapper
para algunosT
De lo contrario, puede lanzarbad_alloc
o cualquier excepción lanzada por la copia o el constructor deF
Ahora, construir o intentar construir (para la resolución de sobrecarga) std::function
desde a [](){}
(es decir, con la firma void(void)
) viola los requisitos de std::function
constructor
[res.on.required] / 1
La violación de las condiciones previas especificadas en una función Requiere: el párrafo da como resultado un comportamiento indefinido, a menos que el párrafo Lanzamientos de la función : especifique lanzar una excepción cuando se viola la condición previa.
Entonces, AFAIK, incluso el resultado de la resolución de sobrecarga no está definido. Por lo tanto, ambas versiones de g ++ / libstdc ++ cumplen este aspecto.
En C ++ 14, esto ha sido cambiado, ver LWG 2132 . Ahora, se requiere la plantilla del constructor de conversión de std::function
para SFINAE-rechazar Callables incompatibles (más sobre SFINAE en el próximo capítulo):
template
function(F f); template function(allocator_arg_t, const A& a, F f); 7 Requiere:
F
debe serCopyConstructible
.8 Observaciones: Estos constructores no participarán en la resolución de sobrecarga a menos que
f
sea invocable (20.9.11.2) para los tipos de argumentosArgTypes...
y el tipo de retornoR
[…]
El “no participará en la resolución de sobrecarga” corresponde al rechazo a través de SFINAE. El efecto neto es que si tienes un conjunto de funciones de sobrecarga foo
,
void foo(std::function); void foo(std::function);
y una expresión de llamada como
foo([](std::string){}) // (C)
luego, la segunda sobrecarga de foo
se elige inequívocamente: dado que std::function
define F
como su interfaz hacia el exterior, la F
define qué tipos de argumentos se pasan a std::function
. Entonces, el objeto de función envuelto tiene que ser llamado con esos argumentos (tipos de argumento). Si se pasa un double
a std::function
, no se puede pasar a una función que toma std::string
, porque no hay conversión double
-> std::string
. Para la primera sobrecarga de foo
, el argumento [](std::string){}
por lo tanto no se considera invocable para std::function
. La plantilla del constructor está desactivada, por lo tanto, no hay una conversión viable de [](std::string){}
a std::function
. Esta primera sobrecarga se elimina del conjunto de sobrecarga para resolver la llamada (C), dejando solo la segunda sobrecarga.
Tenga en cuenta que ha habido un ligero cambio en la redacción anterior, debido a LWG 2420 : Existe una excepción que si el retorno tipo R
de std::function
es void
, entonces se acepta cualquier tipo de devolución (y descartado) para el Callable en la plantilla de constructor mencionada anteriormente. Por ejemplo, tanto []() -> void {}
y []() -> bool {}
son invocables para std::function
. La siguiente situación, por lo tanto, produce una ambigüedad:
void foo(std::function); void foo(std::function); foo([]() -> bool {}); // ambiguous
Las reglas de resolución de sobrecarga no intentan clasificarse entre las diferentes conversiones definidas por el usuario y, por lo tanto, ambas sobrecargas de foo
son viables (en primer lugar) y ninguna de las dos es mejor.
Tenga en cuenta que cuando falla una comprobación de SFINAE, el progtwig no está mal formado, pero la función no es viable para la resolución de sobrecarga. Por ejemplo:
#include #include template auto foo(T) -> typename std::enable_if< std::is_integral::value >::type { std::cout << "foo 1\n"; } template auto foo(T) -> typename std::enable_if< not std::is_integral::value >::type { std::cout << "foo 2\n"; } int main() { foo(42); foo(42.); }
Del mismo modo, una conversión puede hacerse inviable utilizando SFINAE en el constructor de conversión:
#include #include struct foo { template::value >::type > foo(T) { std::cout << "foo(T)\n"; } }; struct bar { template ::value >::type > bar(T) { std::cout << "bar(T)\n"; } }; struct kitty { kitty(foo) {} kitty(bar) {} }; int main() { kitty cat(42); kitty tac(42.); }
Es totalmente valido Dado que las expresiones lambda c ++ 11 (y su envoltorio std::function
) crean objetos de función. La gran fortaleza de los objetos de función es que, incluso cuando son generics, siguen siendo objetos de primera clase. A diferencia de las plantillas de funciones ordinarias, se pueden pasar y devolver desde funciones.
Puede crear conjuntos de sobrecarga del operador de forma explícita con la herencia y el uso de declaraciones. El siguiente uso de Mathias Gaunard demuestra “expresiones lambda sobrecargadas”.
template struct overload_set : F1, F2 { overload_set(F1 x1, F2 x2) : F1(x1), F2(x2) {} using F1::operator(); using F2::operator(); }; template overload_set overload(F1 x1, F2 x2) { return overload_set(x1,x2); } auto f = overload( [](){return 1;}, [](int x){return x+1;} ); int x = f(); int y = f(2);
fuente
EDITAR: Tal vez sea más claro si en el ejemplo provisto reemplazas
F1 -> std::function F2 -> std::function
y verlo comstackr en gcc4.7
La solución con plantilla solo se proporcionó para demostrar que el concepto se adapta al código genérico y que la dessambiguación es posible.
En tu caso, cuando usas un comstackdor anterior como gcc 4.7 , puedes ayudar con un molde explícito y gcc resolverá las cosas, como puedes ver en este ejemplo en vivo
En caso de que te lo estés preguntando, no funcionaría si lanzas al revés (intenta convertir el lambda tomando int a la función std :: sin argumentos, etc.)