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

137

Je souhaite transmettre une fonction surchargée à l' 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, cela (GCC 4.1.2) ne le fait pas. Alors, comment puis-je spécifier ce que f()je veux?

Davka
la source
3
en.cppreference.com/w/cpp/language/overloaded_address
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

137

Vous pouvez utiliser static_cast<>()pour spécifier lequel futiliser en fonction de la signature de 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 également 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

S'il fs'agit d'une fonction membre, vous devez utiliser mem_fun, ou pour votre cas, utiliser la solution présentée dans cet article de Dr. Dobb .

In silico
la source
1
Merci! J'ai toujours un problème, cependant, probablement dû au fait qu'il f()est membre d'une classe (voir l'exemple modifié ci-dessus)
davka
9
@the_drow: La deuxième méthode est en fait beaucoup plus sûre, si l'une des surcharges disparaît, la première méthode donne silencieusement un comportement indéfini, tandis que la seconde détecte le problème au moment de la compilation.
Ben Voigt
3
@BenVoigt Hmm, j'ai testé cela sur vs2010 et je n'ai pas pu trouver de cas où le static_cast n'attraperait pas le problème au moment de la compilation. Il a donné un C2440 avec "Aucune des fonctions avec ce nom dans la portée ne correspond au type de cible". Pouvez-vous clarifier?
Nathan Monteleone
5
@Nathan: Il est possible que je pensais reinterpret_cast. Le plus souvent, je vois des moulages de style C utilisés pour cela. Ma règle est simplement que les casts sur des pointeurs de fonction sont dangereux et inutiles (comme le montre le deuxième extrait de code, une conversion implicite existe).
Ben Voigt
3
Pour les fonctions des membres:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w
29

Lambdas à la rescousse! (Remarque: C ++ 11 requis)

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

Ou en utilisant 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 dissipez l'ambiguïté en supprimant la surcharge (ne fonctionne que pour les fonctions gratuites):

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

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}
milleniumbug
la source
Vive les lambdas! En effet, une excellente solution au problème de résolution de surcharge. (. Je pensais à cela aussi, mais a décidé de le laisser de ma réponse afin de ne pas brouiller les pistes)
aldo
Plus de code pour le même résultat. Je pense que ce n'est pas pour ça que les lambdas ont été faites.
Tomáš Zato - Réintégrer Monica le
@ TomášZato La différence est que cette réponse fonctionne, et que celle acceptée ne fonctionne pas (pour l'exemple posté par OP - vous devez également utiliser mem_fnet bind, qui, BTW. Sont également C ++ 11). Aussi, si nous voulons devenir vraiment pédant, [&](char a){ return f(a); }c'est 28 caractères et static_cast<void (A::*)(char)>(&f)35 caractères.
milleniumbug
1
@ TomášZato Là vous allez coliru.stacked-crooked.com/a/1faad53c4de6c233 je ne sais pas comment rendre cela plus clair
milleniumbug
18

Pourquoi ça ne marche pas

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

Ce serait génial si c'était le cas! Cependant, for_eachest un modèle de fonction, déclaré comme:

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

La déduction de modèle doit sélectionner un type pour UnaryFunctionau moment de l'appel. Mais fn'a pas de type spécifique - c'est une fonction surchargée, il y en a beaucoup favec chacun des types différents. Il n'existe actuellement aucun moyen pour for_eachfaciliter le processus de déduction du modèle en indiquant ce fqu'il veut, donc la déduction du 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

Sauter ici quelques années et C ++ 14 plus tard. Plutôt que d'utiliser un static_cast(qui permettrait à la déduction de modèle de réussir en "corrigeant" ce que fnous voulons utiliser, mais vous oblige à faire manuellement une résolution de surcharge pour "corriger" la bonne), nous voulons que le compilateur fonctionne pour nous. Nous voulons faire appel fà certains arguments. 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 survient fréquemment, nous pouvons donc 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)...); }

puis utilisez-le simplement:

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

Cela fera exactement ce que vous souhaitez que le compilateur fasse - effectuer une résolution de surcharge sur le nom flui-même et faire ce qu'il faut. Cela fonctionnera indépendamment du fait qu'il s'agisse d' fune fonction libre ou d'une fonction membre.

Barry
la source
7

Pas pour répondre à ta question, mais suis-je le seul à trouver

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

à la fois plus simple et plus court que l' for_eachalternative proposée par in silico dans ce cas?

anon
la source
2
probablement, mais c'est ennuyeux :) aussi, si je veux utiliser iterator pour éviter l'opérateur [], cela devient plus long ...
davka
3
@Davka Boring est ce que nous voulons. De plus, les itérateurs ne sont généralement pas plus rapides (peut-être plus lents) que d'utiliser op [, si tel est votre problème.
7
Les algorithmes doivent être préférés aux boucles for, car ils sont moins sujets aux erreurs et peuvent offrir de meilleures opportunités d'optimisation. Il y a un article là-dessus quelque part ... le voici: drdobbs.com/184401446
AshleysBrain
5
@Ashley Jusqu'à ce que je vois des statistiques objectives sur le "moins sujet aux erreurs", je ne vois pas besoin de le croire. Et Meyers dans l'article semble parler de boucles qui utilisent des itérateurs - je parle de l'efficacité des boucles qui n'utilisent PAS d'itérateurs - mes propres benchmarks ont tendance à suggérer que celles-ci sont légèrement plus rapides lorsqu'elles sont optimisées - certainement pas plus lentes.
1
Me voici, je trouve aussi votre solution bien meilleure.
peterh - Réintégrer Monica le
5

Le problème ici ne semble pas être la résolution de surcharge, mais en fait la déduction des paramètres de modèle . Alors que l' excellente réponse de @In silico résoudra un problème de surcharge ambigu en général, il semble que 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" );
}
aldo
la source
4

Si cela ne vous dérange pas d'utiliser C ++ 11, voici une aide intelligente qui est similaire (mais moins moche que) à la distribution statique:

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; }

(Fonctionne pour les fonctions membres; devrait être évident comment le modifier pour qu'il fonctionne pour les fonctions autonomes, et vous devriez être en mesure de fournir les deux versions et le compilateur sélectionnera la bonne pour vous.)

Merci à Miro Knejp d'avoir suggéré: voir aussi https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .

Matthieu
la source
Le problème d'OP est de ne pas pouvoir passer un nom surchargé dans un modèle de fonction, et votre solution consiste à passer un nom surchargé dans un modèle de fonction? C'est exactement le même problème.
Barry
1
@Barry Pas le même problème. La déduction de l'argument de modèle réussit dans ce cas. Cela fonctionne (avec quelques modifications mineures).
Oktalist
@Oktalist Parce que vous fournissez R, ce n'est pas déduit. Il n'y a pas non plus de mention de cela dans cette réponse.
Barry
1
@Barry Je ne fournis pas R, je fournis Args. Ret Tsont déduits. Il est vrai que la réponse pourrait être améliorée. (Il n'y a pas Tdans mon exemple cependant, car ce n'est pas un pointeur vers un membre, car cela ne fonctionnerait pas avec std::for_each.)
Oktalist