1
votes

Passer un gabarit à une fonction sans spécifier le type de béton

Est-il possible de passer un modèle à une fonction sans spécifier le type concret?

J'ai une classe de base qui est un modèle et deux spécialisations différentes de celui-ci. Voici un exemple de la classe de base:

void someFunction(Dataset* dataset)
{
    //do something with the specialization
}

Voici la première classe qui étend la classe de base:

class OnlineDataset : public Dataset<std::string, std::string>
{
public:
    void doSomething(MatrixRm& data) override
    {
     ...
    }
}

Et voici la deuxième classe qui étend la classe de base:

class InMemoryDataset : public Dataset<MatrixRm, MatrixRm>
{
public:
    void doSomething(MatrixRm& data) override
    {
     ...
    }
}

Maintenant, j'aimerais passer l'une de ces classes à une fonction ou à un constructeur d'une classe différente. Mais je ne peux actuellement pas comprendre comment faire cela sans spécifier le type concret.

Voici comment je l'imagine:

template<typename T1, typename T2> class Dataset 
{
protected:
   std::vector<std::pair<T1, T2> > _data_buffer;
public:
   virtual void doSomething(MatrixRm& data) = 0;
}

Dans Visual Studio I obtenez le message d'erreur suivant:

La liste des arguments pour le modèle de classe "Dataset" est manquante

Il me semble logique pourquoi cela n'est pas autorisé, mais y a-t-il un moyen de contourner cela?


1 commentaires

Pourquoi ne pouvez-vous pas utiliser le template void someFunction (Dataset * dataset) ?


3 Réponses :


1
votes

Dans ce cas précis, vous pouvez simplement utiliser une classe de base non basée sur un modèle pour votre interface:

class Dataset {
public:
   virtual void doSomething(MatrixRm& data) = 0;
};

template<typename T1, typename T2> class Dataset_impl : public Dataset
{
protected:
   std::vector<std::pair<T1, T2> > _data_buffer;
};


3 commentaires

Pourquoi vote-t-on contre cela? Je pensais que c'était assez intelligent :)


@Frank: ideone.com/w7bjsz Avec cela, il ne pourra pas passer le type de jeu de données à la fonction foo.


L'ensemble de données de @nomanpouigt OP était déjà abstrait. Donc ce n'est pas comme s'ils pouvaient faire ça en premier lieu



1
votes

Un idiome est de supposer que someFunction ne sera appelé que tous avec un objet Dataset et traiter simplement Dataset comme un paramètre de modèle comme dans :

#include <type_traits>

template<class T>
struct is_dataset: std::false_type{};

template<class T1, class T2>
struct is_dataset<Dataset<T1, T2>>: std::true_type{};

template<class Dataset>
std::enable_if_t<is_dataset<Dataset>::value, void>
someFunction(Dataset dataset){
    // ...
}

Bien sûr, cette solution générerait un code différent pour chaque spécialisation de someFunction pour chaque type unique qui lui est passé.

Cet idiome peut être implémenté de manière plus sûre avec un modèle standard de métaprogrammation comme dans:

template<class Dataset>
void someFunction(Dataset dataset){
    // ...
}

Ce code définit la métafonction is_dataset pour effectuer efficacement un contrôle de temps de compilation est le modèle Dataset transmis est en fait un objet Dataset réel.

Comme vous semblez le savoir, Dataset * code > n'est pas un type réel (ni même un type incomplet). Il fournit simplement une interface pour la construction d'un type réel (par exemple Dataset ).


2 commentaires

Merci! Votre solution a le seul inconvénient que vous avez dit ... la métaprogrammation aura une pénalité de performance, non? Ce ne sera pas suffisant dans mon cas.


Vous ne devriez pas remarquer de pénalité d'exécution. Le seul inconvénient serait (potentiellement) des traductions de code excessives (c'est-à-dire un gonflement du code). Pour un très grand nombre de spécification Dataset , vous pouvez remarquer une augmentation du temps de compilation.



1
votes

Vous pouvez également déclarer la fonction comme modèle:

template<class T1, class T2>
void someFunction(Dataset<T1, T2>* dataset)
{
    //do something with the specialization
}

void foo() {
    InMemoryDataset inMemory;
    someFunction(&inMemory); 
    // Will call someFunction<MatrixRm, MatrixRm>(Dataset<MatrixRm, MatrixRm> *)

    OnlineDataset online;
    someFunction(&online); 
    // will call someFunction<std::string, std::string>(Dataset<std::string, std::string> *)
}


2 commentaires

Merci! someFunction fera la même chose pour les deux types. J'ai utilisé un modèle juste pour séparer la façon dont les données sont stockées dans les ensembles de données. De cette façon, je dois implémenter deux fonctions faisant la même chose, non?


@Bastian Désolé pour la réponse tardive, oui, vous n'implémentez someFunction qu'une seule fois.