150 votes

Comment spécifier un pointeur vers une fonction surchargée ?

Je veux passer une fonction surchargée à la fonction std::for_each() algorithme. Par exemple,

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

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

Je m'attendrais à ce que le compilateur résolve f() par le type d'itérateur. Apparemment, il (GCC 4.1.2) ne le fait pas. Donc, comment puis-je spécifier quel f() Je veux ?

145voto

In silico Points 30778

Vous pouvez utiliser static_cast<>() pour préciser quels f à utiliser en fonction de la signature de la fonction impliquée par le type de pointeur de fonction :

// 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)); 

Ou, vous pouvez aussi faire ceci :

// 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 f est une fonction membre, alors vous devez utiliser la fonction mem_fun ou, dans votre cas, utilisez la solution présentée dans cet article du Dr Dobb. .

0 votes

La deuxième méthode semble très peu sûre, n'est-ce pas ?

1 votes

Merci ! J'ai encore un problème, cependant, probablement dû au fait que f() est un membre d'une classe (voir l'exemple édité ci-dessus)

0 votes

@the_drow : Comment ça ? L'affectation du pointeur de fonction ne fonctionne que s'il existe une surcharge qui correspond à la signature de la fonction impliquée par la déclaration du pointeur de fonction.

37voto

milleniumbug Points 4445

Les lambdas à la rescousse ! (note : C++11 requis)

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

Ou l'utilisation de decltype pour le paramètre lambda :

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

Avec des lambdas polymorphes (C++14) :

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

Ou désambiguïser en supprimant la surcharge (ne fonctionne que pour les fonctions libres) :

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

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

1 votes

Vive les lambdas ! En effet, une excellente solution au problème de la résolution des surcharges. (J'ai pensé à cela aussi, mais j'ai décidé de ne pas le mentionner dans ma réponse pour ne pas brouiller les pistes).

2 votes

Plus de code pour le même résultat. Je pense que ce n'est pas pour ça que les lambdas ont été conçus.

2 votes

@TomášZato La différence est que cette réponse fonctionne, et que la réponse acceptée ne fonctionne pas (pour l'exemple posté par l'OP - vous devez également utiliser mem_fn y bind qui, d'ailleurs, sont également C++11). De plus, si nous voulons être vraiment pédant [&](char a){ return f(a); } est de 28 caractères, et static_cast<void (A::*)(char)>(&f) est de 35 caractères.

21voto

Barry Points 45207

Pourquoi ça ne marche pas

Je m'attendrais à ce que le compilateur résolve f() par le type d'itérateur. Apparemment, il (gcc 4.1.2) ne le fait pas.

Ce serait formidable si c'était le cas ! Cependant, for_each est un modèle de fonction, déclaré comme :

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

La déduction du modèle doit sélectionner un type de UnaryFunction au moment de l'appel. Mais f n'a pas de type spécifique - il s'agit d'une fonction surchargée, il existe de nombreuses f chacun avec des types différents. Il n'existe actuellement aucun moyen pour for_each d'aider le processus de déduction du modèle en indiquant quel f qu'il veut, donc la déduction par modèle échoue tout simplement. Pour que la déduction du modèle réussisse, vous devez travailler davantage sur le site d'appel.

Solution générique pour le réparer

Je fais un saut ici quelques années et C++14 plus tard. Plutôt que d'utiliser un static_cast (ce qui permettrait à la déduction des modèles de réussir en "fixant" ce qui est nécessaire à la réussite de l'opération). f que nous voulons utiliser, mais qui vous oblige à faire manuellement une résolution de surcharge pour "fixer" la bonne), nous voulons faire travailler le compilateur pour nous. Nous voulons appeler f sur certains args. De la manière la plus générique possible, c'est :

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

C'est beaucoup à taper, mais ce genre de problème se pose fréquemment, alors nous pouvons simplement l'envelopper dans une macro (soupir) :

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

et ensuite l'utiliser :

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

Cela fera exactement ce que vous souhaiteriez que le compilateur fasse - effectuer la résolution de surcharge sur le nom f et faire ce qui est juste. Cela fonctionnera indépendamment du fait que f est une fonction libre ou une fonction membre.

5voto

Je ne réponds pas à votre question, mais suis-je le seul qui trouve

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

à la fois plus simple et plus court que le for_each alternative suggérée par in silico dans ce cas ?

2 votes

Probablement, mais c'est ennuyeux :) de plus, si je veux utiliser un itérateur pour éviter l'opérateur [], cela devient plus long...

3 votes

@Davka L'ennui, c'est ce que nous voulons. De plus, les itérateurs ne sont généralement pas plus rapides (voire plus lents) que l'utilisation de op[, si c'est ce qui vous préoccupe.

9 votes

Les algorithmes doivent être préférés aux boucles for, car ils sont moins sujets aux erreurs et offrent de meilleures possibilités d'optimisation. Il y a un article à ce sujet quelque part... le voici : drdobbs.com/184401446

5voto

aldo Points 1583

Le problème ici semble ne pas être résolution des surcharges mais en fait déduction des paramètres du modèle . Alors que le excellente réponse de @In silico résoudra un problème de surcharge ambigu en général, il semble que ce soit la meilleure solution lorsqu'il s'agit de std::for_each (ou similaire) est de spécifier explicitement ses paramètres de modèle :

// 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" );
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X