web-dev-qa-db-fra.com

std :: function ne distingue pas les fonctions surchargées

J'essaie de comprendre pourquoi std::function n'est pas capable de distinguer les fonctions surchargées.

#include <functional>

void add(int,int){}

class A {};

void add (A, A){}

int main(){
        std::function <void(int, int)> func = add;
}

Dans le code présenté ci-dessus, function<void(int, int)> ne peut correspondre qu'à une de ces fonctions et pourtant, il échoue. Pourquoi cela est-il ainsi? Je sais que je peux contourner ce problème en utilisant un lambda ou un pointeur de fonction sur la fonction réelle, puis en stockant le pointeur de fonction dans function. Mais pourquoi cela échoue? Le contexte sur lequel je veux être choisi n'est-il pas clair? Aidez-moi à comprendre pourquoi cela échoue, car je ne suis pas en mesure de comprendre pourquoi la correspondance des modèles échoue dans ce cas.

Les erreurs de compilation que je reçois sur Clang pour cela sont les suivantes:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
      'std::function<void (int, int)>'
        std::function <void(int, int)> func = add;
                                       ^      ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
      candidate constructor not viable: no overload of 'add' matching
      'std::__1::nullptr_t' for 1st argument
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
                              ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
      candidate constructor not viable: no overload of 'add' matching 'const
      std::__1::function<void (int, int)> &' for 1st argument
    function(const function&);
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
      candidate template ignored: couldn't infer template argument '_Fp'
      function(_Fp,
      ^
1 error generated.

EDIT - En plus de la réponse de MSalters, j’ai fait quelques recherches sur ce forum et trouvé la raison exacte pour laquelle cela échoue. J'ai eu la réponse de la réponse de Nawaz dans ce post .

J'ai copié de sa réponse collé ici: 

    int test(const std::string&) {
        return 0;
    }

    int test(const std::string*) {
        return 0;
    }

    typedef int (*funtype)(const std::string&);

    funtype fun = test; //no cast required now!
    std::function<int(const std::string&)> func = fun; //no cast!

Alors pourquoi std::function<int(const std::string&)> ne fonctionne pas de la même manière que funtype fun = test ci-dessus?

La réponse est bien, étant donné que std::function peut être initialisé avec n'importe quel objet, puisque son constructeur est modélisé, ce qui est indépendant de l'argument de modèle que vous avez passé à std::function.

30
MS Srikkanth

Il est évident pour nous de choisir la fonction que vous souhaitez utiliser, mais le compilateur doit suivre les règles du C++ et ne pas utiliser de logiques intelligentes (ou même moins intelligentes que dans des cas simples comme celui-ci!)

Le constructeur pertinent de std::function est:

template<class F> function(F f);

qui est un modèle qui accepte le type any.

La norme C++ 14 contraint le modèle (depuis LWG DR 2132 ) de sorte qu'il:

ne participera pas à la résolution de surcharge sauf si f est appelable (20.9.12.2) pour les types d'argument ArgTypes... et le type de retour R.

ce qui signifie que le compilateur n'autorisera le constructeur à être appelé que lorsque Functor sera compatible avec la signature d'appel de std::function (qui est void(int, int) dans votre exemple). En théorie, cela devrait signifier que void add(A, A) n’est pas un argument viable et que vous souhaitiez donc «évidemment» utiliser void add(int, int).

Cependant, le compilateur ne peut pas tester la contrainte "f est appelable pour les types d'argument ..." jusqu'à ce qu'il connaisse le type de f, ce qui signifie qu'il doit avoir déjà été désambiguïsé entre void add(int, int) et void add(A, A)avant il peut appliquer la contrainte qui lui permettrait de rejeter une de ces fonctions!

Donc, il y a une situation poule et œuf, ce qui signifie malheureusement que vous devez aider le compilateur en spécifiant exactement la surcharge de add que vous voulez utiliser, et then le compilateur peut appliquer la contrainte et (plutôt de manière redondante) décider que c'est un argument acceptable pour le constructeur.

Il est concevable que nous puissions changer le C++ afin que, dans ce cas, all, les fonctions surchargées soient testées par rapport à la contrainte (nous n'avons donc pas besoin de savoir laquelle tester avant de le tester) et si une seule est viable. puis utilisez celui-là, mais ce n'est pas comme ça que C++ fonctionne.

21
Jonathan Wakely

Ce que vous voulez, c'est évident, mais le problème est que std::function ne peut pas influencer la résolution de surcharge de &add. Si vous deviez initialiser un pointeur de fonction brut (void (*func)(int,int) = &add), cela fonctionnerait. En effet, l'initialisation du pointeur de fonction est un contexte dans lequel la résolution de surcharge est effectuée. Le type de cible est exactement connu. Mais std::function prendra presque tous les arguments appelables. Cette flexibilité dans l'acceptation des arguments signifie que vous ne pouvez pas résoudre la surcharge avec &add. Plusieurs surcharges de add pourraient convenir.

Un casting explicite fonctionnera, c'est-à-dire static_cast<void(*)(int, int)> (&add).

Cela peut être emballé dans une template<typename F> std::function<F> make_function(F*) qui vous permettrait d'écrire auto func = make_function<int(int,int)> (&add)

17
MSalters

Essayer:

std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);

Les adresses vers void add(A, A) et void add(int, int) diffèrent objectivement. Lorsque vous pointez la fonction par son nom, il est pratiquement impossible au compilateur de savoir quelle adresse de fonction vous avez besoin. void(int, int) ici n’est pas un indice.

4
W.F.

Une autre solution consiste à utiliser un lambda générique en C++ 14:

int main() {
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}

Cela créera une fonction lambda qui résoudra les problèmes sans ambiguïté ..__ Je n’ai pas transmis d’arguments,

1
Germán Diago