¿Cómo especifico un puntero a una función sobrecargada?


137

Quiero pasar una función sobrecargada al std::for_each()algoritmo. Por ejemplo,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Esperaría que el compilador se resuelva f()por el tipo de iterador. Aparentemente, (GCC 4.1.2) no lo hace. Entonces, ¿cómo puedo especificar cuál f()quiero?


Respuestas:


137

Puede usar static_cast<>()para especificar cuál fusar de acuerdo con la firma de función implícita en el tipo de puntero de función:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

O también puedes hacer esto:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Si fes una función miembro, entonces debe usar mem_fun, o para su caso, usar la solución presentada en este artículo del Dr. Dobb .


1
¡Gracias! Sin embargo, todavía tengo un problema, probablemente debido al hecho de que f()es miembro de una clase (vea el ejemplo editado arriba)
davka

9
@the_drow: el segundo método es en realidad mucho más seguro, si una de las sobrecargas desaparece, el primer método proporciona un comportamiento indefinido en silencio, mientras que el segundo detecta el problema en el momento de la compilación.
Ben Voigt

3
@BenVoigt Hmm, probé esto en vs2010 y no pude encontrar un caso en el que static_cast no detectara el problema en el momento de la compilación. Le dio un C2440 con "Ninguna de las funciones con este nombre en el alcance coincide con el tipo de destino". ¿Puedes aclarar?
Nathan Monteleone

55
@Nathan: Es posible que estuviera pensando reinterpret_cast. Muy a menudo veo moldes de estilo C utilizados para esto. Mi regla es que los lanzamientos en punteros de función son peligrosos e innecesarios (como muestra el segundo fragmento de código, existe una conversión implícita).
Ben Voigt

3
Para funciones de miembro:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

Lambdas al rescate! (nota: se requiere C ++ 11)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

O usando decltype para el parámetro lambda:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

Con lambdas polimórficas (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

O desambiguate eliminando la sobrecarga (solo funciona para funciones gratuitas):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

¡Hurra por las lambdas! De hecho, una excelente solución al problema de resolución de sobrecarga. (Pensé en esto también, pero decidí dejarlo fuera de mi respuesta para no enturbiar las aguas.)
aldo

Más código para el mismo resultado. Creo que para eso no están hechas las lambdas.
Tomáš Zato - Restablece a Mónica el

@ TomášZato La diferencia es que esta respuesta funciona, y la respuesta aceptada no (para el ejemplo publicado por OP, también debe usar mem_fny bind, por cierto, también C ++ 11). Además, si queremos ser realmente pedantes, tenemos [&](char a){ return f(a); }28 caracteres y static_cast<void (A::*)(char)>(&f)35 caracteres.
milleniumbug

1
@ TomášZato Ahí tienes coliru.stacked-crooked.com/a/1faad53c4de6c233 no estoy seguro de cómo aclarar esto
milleniumbug

18

¿Por qué no funciona?

Esperaría que el compilador se resuelva f()por el tipo de iterador. Aparentemente, (gcc 4.1.2) no lo hace.

¡Sería genial si ese fuera el caso! Sin embargo, for_eaches una plantilla de función, declarada como:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

La deducción de plantilla debe seleccionar un tipo UnaryFunctionen el punto de la llamada. Pero fno tiene un tipo específico: es una función sobrecargada, hay muchos fs cada uno con diferentes tipos. No hay una forma actual de for_eachayudar al proceso de deducción de plantillas al indicar cuál fquiere, por lo que la deducción de plantillas simplemente falla. Para que la deducción de la plantilla sea exitosa, debe trabajar más en el sitio de la llamada.

Solución genérica para arreglarlo

Saltando aquí unos años y C ++ 14 más tarde. En lugar de usar un static_cast(que permitiría que la deducción de plantillas tenga éxito al "arreglar" lo fque queremos usar, pero requiere que usted haga manualmente la resolución de sobrecarga para "arreglar" la correcta), queremos que el compilador funcione para nosotros. Queremos recurrir fa algunos argumentos. De la manera más genérica posible, eso es:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

Eso es mucho para escribir, pero este tipo de problema surge de manera molesta con frecuencia, por lo que podemos envolverlo en una macro (suspiro):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

y luego solo utilízalo:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Esto hará exactamente lo que desearía que hiciera el compilador: realizar una resolución de sobrecarga en el nombre fmismo y simplemente hacer lo correcto. Esto funcionará independientemente de si fes una función libre o una función miembro.


7

No para responder a tu pregunta, pero ¿soy el único que encuentra

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

tanto más simple como más corto que la for_eachalternativa sugerida por in silico en este caso?


2
Probablemente, pero es aburrido :) También, si quiero usar iterador para evitar que el operador [], esto se hace más largo ...
davka

3
@Davka Boring es lo que queremos. Además, los iteradores generalmente no son más rápidos (pueden ser más lentos) que usar op [, si es de su incumbencia.

77
Deben preferirse los algoritmos a los bucles, ya que son menos propensos a errores y pueden tener mejores oportunidades de optimización. Hay un artículo sobre eso en alguna parte ... aquí está: drdobbs.com/184401446
AshleysBrain

55
@Ashley Hasta que vea algunas estadísticas objetivas sobre el "menos propenso a errores", no veo la necesidad de creerlo. Y Meyers en el artículo parece hablar de bucles que usan iteradores; estoy hablando de la eficiencia de los bucles que NO usan iteradores; mis propios puntos de referencia tienden a sugerir que estos son marginalmente más rápidos cuando se optimizan, ciertamente no más lentos.

1
Aquí estoy, también encuentro su solución mucho mejor.
peterh - Restablecer Monica

5

El problema aquí parece no ser la resolución de sobrecarga sino la deducción de parámetros de plantilla . Si bien la excelente respuesta de @In silico resolverá un problema de sobrecarga ambiguo en general, parece que la mejor solución cuando se trata std::for_each(o similar) es especificar explícitamente sus parámetros de plantilla :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}

4

Si no te importa usar C ++ 11, aquí hay un ayudante inteligente que es similar (pero menos feo que) al reparto estático:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Funciona para funciones miembro; debe ser obvio cómo modificarlo para que funcione para funciones independientes, y debe poder proporcionar ambas versiones y el compilador seleccionará la correcta para usted).

Gracias a Miro Knejp por sugerir: ver también https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


¿El problema de OP es no poder pasar un nombre sobrecargado a una plantilla de función, y su solución implica pasar un nombre sobrecargado a una plantilla de función? Este es exactamente el mismo problema.
Barry

1
@Barry No es el mismo problema. La deducción de argumentos de plantilla tiene éxito en este caso. Funciona (con algunos pequeños ajustes).
Oktalist

@Oktalist Debido a que está proporcionando R, no se deduce. Tampoco hay mención de eso en esta respuesta.
Barry

1
@Barry no estoy proporcionando R, estoy proporcionando Args. Ry Tse deducen Es cierto que la respuesta podría mejorarse. (No hay ninguno Ten mi ejemplo, porque no es un puntero a miembro, porque eso no funcionaría std::for_each).
Oktalist
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.