Resolución de sobrecarga con la función std ::

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 ser CopyConstructible . f debe ser Callable (20.10.11.2) para tipos de argumentos ArgTypes y tipo de retorno R El constructor de copia y el destructor de A no lanzarán excepciones.

8 Postcondiciones:! !*this si se cumple cualquiera de los siguientes:

  • f es un puntero de función NULL .
  • f es un puntero NULL 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 de f inicializada con std::move(f) . [dejó fuera una nota aquí]

10 Lanzamientos: no lanzará excepciones cuando f sea ​​un puntero a la función o un reference_wrapper para algunos T De lo contrario, puede lanzar bad_alloc o cualquier excepción lanzada por la copia o el constructor de F

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 ser CopyConstructible .

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 argumentos ArgTypes... y el tipo de retorno R

[…]

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.


¿Cómo puede SFINAE ayudar aquí?

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.)