web-dev-qa-db-fra.com

Équivalent C ++ de l'utilisation de <T extend Class> pour un paramètre Java paramètre / type de retour

En Java, pour créer une fonction qui renvoie un objet qui est du même type qu'un paramètre et étend une certaine classe, je taperais:

public <T extends MyClass> T foo(T bar) {...}

Existe-t-il un équivalent C++ de cela?

En d'autres termes, comment puis-je créer une fonction qui prend n'importe quelle classe qui étend une certaine classe et renvoie ce même type? (C'est dans le but de classes virtuelles abstraites/pures).

21
ricky3350

On peut utiliser enable_if ici si vous disposez de C++ 11 ou supérieur

template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
    return T();
}

Par exemple:

class MyClass
{
public:
    int a = 1;
};

class Derived : public MyClass
{
public:
    int b = 2;
};

class NotDerived
{
public:
    int b = 3;
};

template<typename T, typename std::enable_if<std::is_base_of<MyClass, T>::value>::type* = nullptr>
T Foo(T bar)
{
    return T();
}

int main()
{
    Derived d;
    NotDerived nd;
    std::cout << Foo(d).b << std::endl;; // works
    //std::cout << (Foo(nd)).b << std::endl;; //compiler error

    return 0;
}

Démo en direct

13
AndyG

Techniquement, comme les autres réponses le montrent, il existe des moyens de le restreindre aux sous-types d'un certain type au moment de la compilation. Cependant, la plupart du temps, vous

template <typename T> T foo(T bar) {...}

sans avoir besoin de spécifier une limite.

En Java, des limites sont nécessaires pour les génériques car la classe ou la méthode générique est compilée séparément de toute utilisation de celle-ci. Les classes ou méthodes génériques sont compilées une seule fois, en une seule version dans le bytecode, une version unique capable de gérer tous les arguments que les appelants lui lancent et qui satisfont aux limites de sa déclaration.

Le compilateur doit vérifier les utilisations du type T dans le corps de la méthode, comme les appels de méthode, les accès aux champs, etc., sans savoir ce qu'est T, vous devez donc fournir une limite Ainsi, le compilateur peut être convaincu que, par exemple, un appel de méthode est valide car il est défini sur tous les types qui satisfont cette limite. Par exemple, si vous aviez l'expression bar.baz() dans le corps de la méthode, le compilateur ne vous permettra de compiler que si le type MyClass (et donc tous ses sous-types) fournit la méthode .baz(); si vous n'aviez fourni aucune limite, le compilateur se plaindrait que Object (la limite supérieure implicite) n'a pas de méthode .baz().

Les modèles C++ sont différents. La classe ou la fonction de modèle est "instanciée" (compilée à nouveau) pour chaque argument de type différent pour lequel elle est utilisée. Ainsi, au moment de la compilation du corps de la fonction pour un T particulier, le compilateur sait ce qu'est T et est capable de vérifier directement les utilisations de ce type.

Donc, si vous aviez l'expression bar.baz() dans le corps de la fonction, ce serait bien. Si vous avez utilisé cette fonction avec T étant un type qui étend MyClass, alors elle se compilera très bien, car un tel type a une .baz(). Si vous utilisez cette fonction avec un type qui n'a pas de .baz(), alors elle ne pourra pas être compilée à cette utilisation. Si vous utilisez accidentellement la fonction avec un type qui n'étend pas MyClass mais a une .baz() dont les types de paramètre et le type de retour correspondent à la façon dont vous l'utilisez, elle se compilera également; mais ce n'est pas nécessairement une mauvaise chose. Les modèles C++ ne sont généralement pas utilisés avec des hiérarchies de types, mais plutôt avec des exigences sur ce que le type doit fournir. Ainsi, par exemple, un algorithme de tri ne va pas exiger que son conteneur et/ou son type d'élément étendent un certain type, mais plutôt que le conteneur fournisse certaines fonctionnalités (par exemple, un opérateur d'indice d'accès aléatoire), et que le type d'élément fournisse certaines fonctionnalités (par exemple un opérateur inférieur à).

15
newacct

Comme je ne peux pas commenter la réponse acceptée, je fournis une nouvelle réponse qui s'appuie sur elle.

Les paramètres du modèle peuvent être simplifiés en ayant le enable_if la condition devient paramètre de modèle de type par défaut au lieu de nullptr.

template<typename T, typename = std::enable_if<std::is_base_of<MyClass, T>::value>>
6
Matthew Borger