std:: function falla al distinguir funciones sobrecargadas


Estoy tratando de entender por qué std::function no es capaz de distinguir entre funciones sobrecargadas.

#include <functional>

void add(int,int){}

class A {};

void add (A, A){}

int main(){
        std::function <void(int, int)> func = add;
}

En el código mostrado arriba, function<void(int, int)> solo puede coincidir con una de estas funciones y, sin embargo, falla. ¿Por qué es así? Sé que puedo trabajar alrededor de esto usando un lambda o un puntero de función a la función real y luego almacenar el puntero de función en función. Pero, ¿por qué falla esto? ¿No está claro el contexto en qué función quiero ser elegido? Por favor, ayúdame a entender por qué esto falla como No puedo entender por qué falla la coincidencia de plantillas en este caso.

Los errores del compilador que obtengo en clang para esto son los siguientes:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
      'std::function<void (int, int)>'
        std::function <void(int, int)> func = add;
                                       ^      ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
      candidate constructor not viable: no overload of 'add' matching
      'std::__1::nullptr_t' for 1st argument
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
                              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
      candidate constructor not viable: no overload of 'add' matching 'const
      std::__1::function<void (int, int)> &' for 1st argument
    function(const function&);
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
      candidate template ignored: couldn't infer template argument '_Fp'
      function(_Fp,
      ^
1 error generated.

EDITAR - Además de la respuesta de MSalters, hice algunas búsquedas en este foro y encontré la razón exacta por la que esto falla. Recibí la respuesta de la respuesta de Nawaz en este post .

Tengo copia pegada de su respuesta aquí:

    int test(const std::string&) {
        return 0;
    }

    int test(const std::string*) {
        return 0;
    }

    typedef int (*funtype)(const std::string&);

    funtype fun = test; //no cast required now!
    std::function<int(const std::string&)> func = fun; //no cast!

Entonces, ¿por qué std::function<int(const std::string&)> no funciona de la manera que funtype fun = test funciona arriba?

Bien el la respuesta es, porque std::function se puede inicializar con cualquier objeto, ya que su constructor está templatizado, que es independiente del argumento de plantilla que pasó a std::function.

Author: Community, 2015-05-22

5 answers

Es obvio para nosotros qué función pretende ser elegido, pero el compilador tiene que seguir las reglas de C++ no utilizar saltos inteligentes de la lógica (o incluso no tan inteligentes, como en casos simples como este!)

El constructor relevante de std::function es:

template<class F> function(F f);

Que es una plantilla que acepta cualquier tipo.

El estándar C++14 limita la plantilla (desde LWG DR 2132 ) de modo que:

No participará en la sobrecarga resolution unless f is Callable (20.9.12.2) for argument types ArgTypes... and return type R.

Lo que significa que el compilador solo permitirá que el constructor sea llamado cuando Functor sea compatible con la firma de llamada del std::function (que es void(int, int) en su ejemplo). En teoría, eso debería significar que void add(A, A) no es un argumento viable y, por lo tanto, "obviamente" pretendías usar void add(int, int).

Sin embargo, el compilador no puede probar el " f es llamable para tipos de argumento ..."restricción hasta que conozca el tipo de f, lo que significa que necesita ya haber desambiguado entre void add(int, int) y void add(A, A) antes de puede aplicar la restricción que le permitiría rechazar una de esas funciones!

Así que hay una situación de gallina y huevo, lo que desafortunadamente significa que necesita ayudar al compilador especificando exactamente qué sobrecarga de add desea usar, y entonces el compilador puede aplicar la restricción y (más bien redundantemente) decidir que es un argumento aceptable para el constructor.

Es concebible que podamos cambiar C++ para que en casos como este todas las funciones sobrecargadas se prueben contra la restricción (por lo que no necesitamos saber cuál probar antes de probarlo) y si solo una es viable, use esa, pero así no es como funciona C++.

 21
Author: Jonathan Wakely,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-22 10:52:57

Si bien es obvio lo que quieres, el problema es que std::function no puede influir en la resolución de sobrecarga de &add. Si inicializa un puntero de función raw (void (*func)(int,int) = &add), funciona. Esto se debe a que la inicialización del puntero de función es un contexto en el que se realiza la resolución de sobrecarga. El tipo de destino es exactamente conocido. Pero std::function tomará casi cualquier argumento que sea invocable. Esa flexibilidad en aceptar argumentos significa que no se puede hacer resolución de sobrecarga en &add. Múltiples sobrecargas de add podría ser adecuado.

Un elenco explícito funcionará, es decir, static_cast<void(*)(int, int)> (&add).

Esto se puede envolver en un template<typename F> std::function<F> make_function(F*) que le permitiría escribir auto func = make_function<int(int,int)> (&add)

 17
Author: MSalters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-22 10:04:37

Intenta:

std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);

Se dirige a void add(A, A) y void add(int, int) obviamente difieren. Cuando apuntas a la función por su nombre es prácticamente imposible para el compilador saber qué dirección de función necesitas. void(int, int) aquí no hay una pista.

 4
Author: W.F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-22 09:55:31

Otra forma de lidiar con esto es con una lambda genérica en C++14:

int main() {
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}

Que creará una función lambda que resolverá las cosas sin ambigüedad. No presenté argumentos,

 1
Author: Germán Diago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-12 13:30:24

Por lo que puedo ver, es un problema de Visual Studio.

C++11 standard (20.8.11)

std::function synopsis
template<class R, class... ArgTypes> class function<R(ArgTypes...)>;

Pero VisualStudio no tiene esa especialización

Clang++ y g++ están perfectamente bien con la sobrecarga std:: functions

Las respuestas anteriores explican por qué VS no funciona, pero no mencionaron que es VS' bug

 -2
Author: Dimitry Markman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-07 01:29:42