39 votes

N'est-ce pas l'argument de modèle (la signature) de std::function partie de son type?

Étant donné le code suivant, quelle est la raison derrière l'ambiguïté? Puis-je contourner ou vais-je garder l' (ennuyeux) conversions explicites?

#include <functional>

using namespace std;

int a(const function<int ()>& f)
{
    return f();
}

int a(const function<int (int)>& f)
{
    return f(0);
}

int x() { return 22; }

int y(int) { return 44; }

int main()
{
    a(x);  // Call is ambiguous.
    a(y);  // Call is ambiguous.

    a((function<int ()>)x);    // Works.
    a((function<int (int)>)y); // Works.

    return 0;
}

Fait intéressant, si j'en commentaire l' a() fonction avec l' function<int ()> paramètre et appelez - a(x) dans ma main, la compilation correctement échoue en raison de l'incompatibilité de type entre x et l'argument function<int (int)> seulement de l' a() fonction disponible. Si le compilateur ne parvient pas, dans ce cas, pourquoi y aurait-il une ambiguïté lorsque les deux a() fonctions sont présentes?

J'ai essayé avec VS2010 et g++ v. 4.5. Les deux me donner exactement la même ambiguïté.

40voto

Xeo Points 69818

Le problème est que les deux function<int()> et function<int(int)> sont constructibles à partir de la même fonction. C'est ce que le constructeur déclaration de std::function ressemble dans VS2010:

template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);

Ignorant la SFINAE partie, il est constructible à partir de pratiquement n'importe quoi.
std::/boost::function utilisent une technique appelée type d'effacement, afin de permettre arbitraire des objets/fonctions passées, tant qu'ils satisfont à la signature lors de l'appelé. Un inconvénient de cette est que vous obtenez un message d'erreur dans la partie la plus profonde de la mise en œuvre (où l'sauvé la fonction est appelée), lors de la fourniture d'un objet qui ne peut pas être appelé comme la signature veut, au lieu de dans le constructeur.


Le problème peut être illustré par ce peu de classe:

template<class Signature>
class myfunc{
public:
    template<class Func>
    myfunc(Func a_func){
        // ...
    }
};

Maintenant, lorsque le compilateur recherche valide les fonctions de la surcharge de jeu, il essaie de convertir les arguments si pas parfait ajustement de la fonction existe. La conversion peut se faire par le constructeur de la paramètre de la fonction, ou par l'intermédiaire d'un opérateur de conversion de l'argument donné à la fonction. Dans notre cas, c'est l'ancien.
Le compilateur essaie de la première surcharge de a. Pour le rendre viable, il doit faire une conversion. Pour convertir un int(*)() d'un myfunc<int()>, il tente le constructeur de myfunc. Être un modèle qui prend n'importe quoi, la conversion naturellement réussit.
Maintenant, il tente la même chose avec le deuxième surcharge. Le constructeur étant toujours le même et toujours rien donné, la conversion fonctionne aussi.
Être de gauche avec 2 fonctions dans la surcharge de jeu, le compilateur est un panda triste et ne sait pas quoi faire, alors il a simplement dit que l'appel est ambiguë.


Donc en fin de compte, l' Signature partie du modèle appartient au type lors de la prise de déclarations et les définitions, mais n'est pas quand vous voulez pour construire un objet.


Edit:
Avec toute mon attention sur le répondeur le titre de la question, j'ai totalement oublié votre deuxième question. :(

Puis-je contourner ou vais-je garder l' (ennuyeux) conversions explicites?

Autant que je sache, vous avez 3 options.

  • Garder le casting
  • Faire un function objet du type approprié et de l'

    function<int()> fx = x; function<int(int)> fy = y; a(fx); a(fy);

  • Masquer le long de la coulée dans une fonction et l'utilisation TMP pour obtenir le droit de signature

La TMP (modèle de la métaprogrammation) version est assez verbeux et de code réutilisable, mais il se cache le casting de la part du client. Un exemple peut être trouvé ici, qui repose sur l' get_signature metafunction qui est spécialisée partielle sur la fonction des types pointeur (et fournit un bon exemple de la manière dont la correspondance de motif peut travailler en C++):

template<class F>
struct get_signature;

template<class R>
struct get_signature<R(*)()>{
  typedef R type();
};

template<class R, class A1>
struct get_signature<R(*)(A1)>{
  typedef R type(A1);
};

Bien sûr, cela doit être étendu pour le nombre d'arguments que vous voulez soutenir, mais c'est fait une fois et puis enterré dans un "get_signature.h" - tête. :)

Une autre option que je considère mais immédiatement jetés était SFINAE, qui serait d'introduire encore plus de code réutilisable, que le TMP version.

Donc, oui, que sont les options que je connais. Espérons que l'un d'entre eux travaille pour vous. :)

11voto

Howard Hinnant Points 59526

J'ai vu cette question de monter une fois de trop. libc++ maintenant compile ce code sans ambiguïté (comme conforme extension).

4voto

ecatmur Points 64173

Voici un exemple de la façon d'envelopper std::function dans une classe qui vérifie invokability de ses paramètres du constructeur:

template<typename> struct check_function;
template<typename R, typename... Args>
struct check_function<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_same<R, void>::value
            || std::is_convertible<
                decltype(std::declval<T>()(std::declval<Args>()...)),
                R>::value>::type>
        check_function(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};

Utiliser comme ceci:

int a(check_function<int ()> f) { return f(); }
int a(check_function<int (int)> f) { return f(0); }

int x() { return 22; }
int y(int) { return 44; }

int main() {
    a(x);
    a(y);
}

Notez que ce n'est pas tout à fait la même que la surcharge sur la fonction de signature, comme il traite les convertibles argument (et retour), les types comme équivalent. Exacte de la surcharge, cela devrait fonctionner:

template<typename> struct check_function_exact;
template<typename R, typename... Args>
struct check_function_exact<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_convertible<T, R(*)(Args...)>::value>::type>
        check_function_exact(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};

2voto

Josh Points 679

std::function<T> a une conversion ctor qui prend un type arbitraire (c'est à dire, autre chose qu'un T). Bien sûr, dans ce cas, que ctor le résultat serait une erreur d'incompatibilité de type, mais le compilateur ne veut pas en arriver là -- l'appel est ambigu, tout simplement parce que le ctor existe.

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