2013-02-08 2 views
1

Je suis tombé sur un cas d'utilisation où std::mem_fn ne peut pas faire quelque chose qu'une fonction wrapper roulée à la main peut faire. Il apparaît lorsque la fonction enveloppe est utilisée sur quelque chose qui est pas de la classe de la méthode, mais un type implicitement convertible en elle:Déficience dans std :: mem_fn par rapport au foncteur roulé à la main

#include <functional> 

struct A 
{ 
}; 

struct B 
{ 
    B(A); // implicit conversion from A to B 
    void foo() const; 
}; 


auto foo1 = std::mem_fn(&B::foo);  // std::mem_fn 

void foo2(const B& b) { b.foo(); } // hand-rolled wrapper 

int main() 
{ 
    A a; 
    foo1(a); // doesn't work 
    foo2(a); // works fine 
} 

L'erreur du compilateur pour l'appel à foo1 est le suivant (avec GCC 4.8):

In file included from test.cpp:1:0: 
functional: In instantiation of '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::_M_call(_Tp&, const volatile void*, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]': 
functional:608:42: required from '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::operator()(_Tp&, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]' 
test.cpp:21:11: required from here 
functional:586:13: error: no match for 'operator*' (operand type is 'A') 
    { return ((*__ptr).*__pmf)(std::forward<_ArgTypes>(__args)...); } 
      ^

Aurait-il été possible de mettre en œuvre std::mem_fn de telle sorte que ce cas d'utilisation fonctionne comme il le fait avec l'emballage roulé à la main?

Répondre

5

Il serait possible, oui, mais ce n'est pas la façon dont la norme C++ spécifie mem_fn.

La norme dit que foo1(a) appelle INVOKE(&B::foo, a) lorsque cela est défini dans [func.require] comme:

Définir INVOKE (f, t1, t2, ..., tN) comme suit:
- (t1.*f)(t2, ..., tN) lorsque f est un pointeur vers une fonction membre d'une classe T et t1 est un objet du type T ou une référence à un objet du type T ou une référence à un objet d'un type dérivé de T;
- ((*t1).*f)(t2, ..., tN) lorsque f est un pointeur vers une fonction membre d'une classe T et t1 ne fait pas partie des types décrits dans l'élément précédent;
- ...

Votre cas ne satisfait pas aux conditions de la première balle, parce a n'est pas un objet de classe B, ni une référence à un B ou une classe dérivée de B, de sorte que la seconde bullet s'applique et donc c'est équivalent à ((*a).*f)() qui n'est pas valide.

Il est défini de cette manière pour permettre l'utilisation de pointeurs intelligents, par ex.

auto foo1 = std::mem_fn(B::foo); 
auto p = std::make_shared<B>(); 
foo1(p); 

La définition de INVOKE (qui est également utilisé par bind, function, async et d'autres parties de la bibliothèque qui créent des enveloppes d'appel) signifie que lors de l'appel d'un pointeur à élément enroulé, si le premier l'argument t1 n'est pas un T alors il est supposé être une sorte de pointeur et est déréférencé. Cela signifie que cela fonctionne avec std::shared_ptr et std::unique_ptr mais aussi avec des types que std::mem_fn ne sait rien sur, tels que boost::shared_ptr et MyVeryOwnSmartPtr.

Pour faire votre travail de code, il serait possible d'ajouter des cas supplémentaires à gérer quand t1 est pas un T ou un type dérivé de T, mais is_convertible<T>::value est vrai, et d'invoquer T(t1).*f)(), mais cela compliquerait la spécification et la puissance avoir des conséquences indésirables dans certains cas. Votre "wrapper" forcera la conversion implicite de son argument, mais il ne peut pas gérer les pointeurs intelligents ou les rvalues ​​de type B, qui sont tous deux supportés par mem_fn.Si vous avez un cas spécifique où vous voulez convertir des objets A à B pour appeler la fonction, alors faites juste cela, le modèle générique mem_fn ne convient pas, mais il est plus flexible et générique et fonctionne dans beaucoup d'autres situations.

(NB la définition de INVOKE est en fait défectueux parce qu'il déréférence std::reference_wrapper objets de la même manière que déréférence votre argumentation a. J'ai proposé une solution à http://cplusplus.github.com/LWG/lwg-active.html#2219, mais cela ne touche pas votre exemple.)

Questions connexes