Déduction de modèle pour la fonction en fonction de son type de retour?

J'aimerais pouvoir utiliser la déduction de modèle pour réaliser ce qui suit:

GCPtr<A> ptr1 = GC::Allocate();
GCPtr<B> ptr2 = GC::Allocate();

Au Lieu de (ce que j'ai actuellement):

GCPtr<A> ptr1 = GC::Allocate<A>();
GCPtr<B> ptr2 = GC::Allocate<B>();

Ma fonction D'allocation actuelle ressemble à ceci:

class GC
{
public:
    template <typename T>
    static GCPtr<T> Allocate();
};

Serait-ce possible de supprimer les et supplémentaires?

Merci

26
demandé sur Ashwin Nanjappa 2010-04-10 14:09:41

6 réponses

Cela ne peut pas être fait. Le type de retour ne participe pas à la déduction de type, il est plutôt le résultat d'avoir déjà apparié la signature du modèle approprié. Vous pouvez néanmoins le cacher de la plupart des utilisations comme:

// helper
template <typename T>
void Allocate( GCPtr<T>& p ) {
   p = GC::Allocate<T>();
}

int main()
{
   GCPtr<A> p = 0;
   Allocate(p);
}

Si cette syntaxe est réellement meilleure ou pire que la GCPtr<A> p = GC::Allocate<A>() initiale est une autre question.

P.S. c++11 vous permettra d'ignorer l'une des déclarations de type:

auto p = GC::Allocate<A>();   // p is of type GCPtr<A>
29
répondu David Rodríguez - dribeas 2010-04-10 10:14:51

La seule chose à laquelle je pense: faire Allouer un non-modèle qui renvoie un non-modèle d'objet proxy qui est basé sur un modèle de conversion de l'opérateur qui fait le travail réel:

template <class T>
struct GCPtr
{

};

class Allocator
{
public:
    template <class T>
    operator GCPtr<T>() { return GCPtr<T>(); }
};

class GC
{
public:
    static Allocator Allocate() { return Allocator(); }//could give a call-back pointer?
};

int main()
{
    GCPtr<int> p = GC::Allocate();
}
22
répondu UncleBens 2010-04-10 10:40:27

Vous pourriez suivre la voie opposée.

Si vous utilisez un compilateur à jour (MSVC 2010 qui devrait sortir dans quelques jours, ou la version actuelle de GCC) et ne me dérange pas de compter sur les fonctionnalités c++0x:

auto ptr1 = GC::Allocate<A>();
auto ptr2 = GC::Allocate<B>();

Vous épargnerait les <A> et <B> supplémentaires, mais pas sur le côté droit. :)

7
répondu jalf 2010-04-10 11:09:28

(Cette réponse est la même que @UncleBens, mais un peu plus générale car elle est parfaite-avancer tous les arguments.)

Ceci est très utile dans des langages comme haskell où, par exemple, read prendra une chaîne en entrée et l'analysera en fonction du type de retour souhaité.

(Ici exemple de code sur ideone.)

Commencez par la fonction foo dont nous souhaitons déduire le type de retour:

template<typename Ret>
Ret foo(const char *,int);
template<>
std::string foo<std::string>(const char *s,int) { return s; }
template<>
int         foo<int        >(const char *,int i) { return i; }

Lorsqu'on lui demande une chaîne, elle retourne la chaîne c'est dans son premier argument. Lorsqu'on lui demande un int, il retournera le deuxième argument.

Nous pouvons définir une fonction auto_foo qui peut être utilisée comme suit:

int main() {
        std::string s = auto_foo("hi",5); std::cout << s << std::endl;
        int         i = auto_foo("hi",5); std::cout << i << std::endl;
}

Pour que cela fonctionne, nous avons besoin d'un objet qui stockera temporairement les arguments de la fonction, et exécutera également la fonction quand on lui demande de convertir au type de retour souhaité:

#include<tuple>

template<size_t num_args, typename ...T>
class Foo;
template<typename ...T>
class Foo<2,T...> : public std::tuple<T&&...>
{
public: 
        Foo(T&&... args) :
                std::tuple<T&&...>(std::forward<T>(args)...)
        {}
        template< typename Return >
        operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this)); }
};
template<typename ...T>
class Foo<3,T...> : std::tuple<T&&...>
{
public: 
        Foo(T&&... args) :
                std::tuple<T&&...>(std::forward<T>(args)...)
        {}
        template< typename Return >
        operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this), std::get<2>(*this)); }
};

template<typename ...T>
auto
auto_foo(T&&... args)
        // -> Foo<T&&...> // old, incorrect, code
        -> Foo< sizeof...(T), T&&...> // to count the arguments
{
        return              {std::forward<T>(args)...};
}

En outre, ce qui précède fonctionne pour les fonctions deux-arg ou trois-arg, il n'est pas difficile de voir comment étendre que.

C'est beaucoup de code à écrire! Pour chaque fonction à laquelle vous allez l'appliquer, vous pouvez écrire une macro qui le fait pour vous. Quelque chose comme ceci en haut de votre fichier:

REGISTER_FUNCTION_FOR_DEDUCED_RETURN_TYPE(foo); // declares
                        // necessary structure and auto_???

, puis vous pouvez utiliser auto_foo dans votre programme.

4
répondu Aaron McDaid 2014-05-27 15:00:00

De la même manière, vous ne pouvez pas surcharger les fonctions sur le type de retour, vous ne pouvez pas faire de déduction de modèle dessus. Et pour la même raison - si f() est un template/overload qui renvoie quelque chose, quel type utiliser ici:

f();
1
répondu 2010-04-10 10:19:27

Vous pouvez essayer d'utiliser une macro pour cela. Autre que cela, je ne vois pas comment cela est censé fonctionner avec une seule déclaration.

#define ALLOC(ptrname,type) GCPtr<type> ptrname = GC::Allocate<type>()

ALLOC(ptr1,A);

Les points de Johannes sont valables. Le >> problème est facilement résolu. Mais je pense que le fait d'avoir des virgules dans le type nécessite l'extension varargs du préprocesseur C99:

#define ALLOC(ptrname,...) GCPtr< __VA_ARGS__ > ptrname = GC::Allocate< __VA_ARGS__ >()

ALLOC(ptr1,SomeTemplate<int,short>);
0
répondu sellibitze 2010-04-10 11:02:58