2017-10-16 2 views
1

Je suis tenté de créer une surcharge de fonction afin qu'elle ne lie (fonctionne) qu'à une fonction membre. Je pris un coup d'oeil à la signature de la fonction de std::mem_fn http://en.cppreference.com/w/cpp/utility/functional/mem_fnSyntaxe de modèle pour ne remplacer que les fonctions membres

template <class Ret, class T> 
/* unspecified */ mem_fn (Ret T::* pm); 

Je structuré mes paramètres en tant que tels

template <typename R, typename F> 
auto call_me(R C::* func) { 
    return (mContainer.*func); 
} 

Cependant, je puis obtenir cette erreur

.\template.cpp: In function 'int main()': 
.\template.cpp:29:49: error: no matching function for call to 'MyClass<int, std::vector<int> >::call_me(std::vector<int>::size_type (std::vector<int>::*)() const noexcept)' 
    cout << test.call_me(&std::vector<int>::size) << endl; 
               ^
.\template.cpp:16:10: note: candidate: template<class R, class F> auto MyClass<T, C>::call_me(R C::*) [with R = R; F = F; T = int; C = std::vector<int>] 
    auto call_me(R C::* func) { 
      ^~~~~~~ 
.\template.cpp:16:10: note: template argument deduction/substitution failed: 
.\template.cpp:29:49: note: couldn't deduce template parameter 'F' 
    cout << test.call_me(&std::vector<int>::size) << endl; 

La raison pour laquelle je Je suis en train de faire cela est donc je peux avoir une surcharge qui fonctionne pour les objets lambda et fonctionnels généraux et une autre surcharge qui fonctionne pour membe r pointeurs fonctionnels. Voici un exemple minimal de ce que j'essaie d'accomplir. Je sais que cette question prête un peu à confusion, alors n'hésitez pas à demander des éclaircissements si nécessaire.

#include <vector> 
#include <iostream> 

using namespace std; 

template <typename T, typename C> 
struct MyClass { 
    // This doesnt work because of SFINAE 

    template <typename F, typename... A> 
    auto call_me(F func, A... args) { // lambda version 
     return func(args...); 
    } 

    template <typename R, typename F> 
    auto call_me(R C::* func) { // member function version 
     return (mContainer.*func); 
    } 

    C mContainer; // this is private in my actual code 

}; 


int main() { 
    MyClass<int, std::vector<int> > test;; 

    // these two calls will call the member function version of the overload 
    cout << test.call_me(&std::vector<int>::size) << endl; 

    using insert_func_t = std::vector<int>::iterator(std::vector<int>::*)(std::vector<int>::const_iterator, const int&); 
    test.call_me(static_cast<insert_func_t>(&std::vector<int>::insert), test.mContainer.begin(), 4); 

    // this call will call the lambda version of the overload 
    cout << test.call_me([](std::vector<int>& in){ in.push_back(5); }); 

    return 0; 
} 
+1

'std :: invoke' fournit une syntaxe uniforme des appels. Si vous ne l'avez pas encore dans votre bibliothèque standard, il n'a pas besoin de fonctionnalités C++ 17 pour écrire, et il existe des implémentations distinctes. En ayant une fonction 'invoke', vous n'avez pas besoin de polluer le reste du code avec les différences d'appel. – chris

+0

@chris Je viens de vérifier cela, ça me laisse toujours le problème de savoir si le paramètre fourni est une fonction membre ou un objet appelable. – Aryan

+0

@IgorTandetnik hmm, je l'ai essayé avec ça aussi, mais ça ne marche toujours pas R (C :: * func)() 'et avec R (C :: * func) (Args ...)' avec 'Args' étant un autre paramètre de template – Aryan

Répondre

4

Vous pouvez couvrir les deux cas avec std::invoke:

template <typename F, typename... A> 
auto call_me(F func, A... args) { // lambda version 
    return std::invoke(func, mContainer, args...); 
} 

Pour un objet de fonction, comme votre fermeture, cela exige operator(). Pour une fonction membre, elle préfixe les arguments avec l'objet à utiliser, puis l'appelle avec la syntaxe appropriée. En d'autres termes, votre travail est déjà fait.

Vous pouvez également prendre en compte le transfert parfait:

template <typename F, typename... A> 
auto call_me(F&& func, A&&... args) { // lambda version 
    return std::invoke(std::forward<F>(func), mContainer, std::forward<A>(args)...); 
} 
0

La fonction (ou méthode de classe) est plus importante que le type de retour.

Les fonctions ont des arguments. Chaque argument a un type. Les types d'arguments de fonction font partie de la signature de la fonction. Cela inclut les fonctions avec zéro argument.

Si vous souhaitez qu'une fonction modèle se lie à la fonction membre d'une classe arbitraire, la fonction modèle doit également lier explicitement les types d'arguments de la fonction membre de la classe. Ceci est un aspect fondamental du type de sécurité de C++:

template<class Ret, 
    class T, 
    class ...Args> 
void mem_fn(Ret (T::*pm)(Args...)) 
{ 
} 

class foo { 

public: 

    void bar(int); 
}; 

void foo() 
{ 
    mem_fn(&foo::bar); 
} 

Si vous êtes intéressé ne se lier aux fonctions de membres qui ne prennent pas les arguments que vous pouvez renoncer à les paramètres du modèle variadique, et vous devez lier explicitement le modèle en conséquence: Notez que ce modèle se liera uniquement à une fonction de membre de classe qui ne prend pas d'arguments.Il ne sera pas lié à une fonction membre void bar(int), par exemple.

+0

Et qualificatif cv/ref (et C ellipsis) -> 'Ret (C :: *) (Args ..., ...) const volatile &&'. – Jarod42

+0

@ Jarod42 Dois-je avoir des surcharges pour chacun? (const, const volatile, const noexcept ... etc)? ou y a-t-il un moyen de le simplifier? – Aryan

+0

@Aryan: Si vous ne comptez pas sur quelque chose qui gère déjà cela (comme 'std :: invoke'), vous devrez malheureusement gérer cela vous-même. – Jarod42