17 votes

comment déclarer correctement le modèle prenant un type de fonction en paramètre (comme une std::function)

J'ai découvert qu'il n'est pas trivial d'avoir une syntaxe soignée comme dans :

std::function<int(float, bool)>

Si je déclare la fonction comme :

template <class RetType, class... Args>
class function {};

Il s'agirait d'une syntaxe ordinaire pour définir les types de fonctions modélisés :

function<int,float,bool> f;

Mais cela fonctionne avec une astuce étrange, à savoir la spécialisation partielle des modèles.

template <class> class function; // #1

template <class RV, class... Args>
class function<RV(Args...)> {} // #2

Comment cela se fait-il ? Pourquoi je dois donner au modèle une forme générale vide avec un paramètre de type vide (#1) ou sinon il ne compilera pas.

9voto

TartanLlama Points 1461

C'est ainsi que le langage a été conçu. Les modèles primaires ne permettent pas une décomposition complexe des types comme celle-ci ; vous devez utiliser la spécialisation partielle.

Si je comprends bien, vous aimeriez écrire la deuxième version sans avoir à fournir le modèle principal. Mais réfléchissez à la manière dont les arguments du modèle correspondent aux paramètres du modèle :

template <class RV, class Arg1, class... Args>
class function<RV(Arg1, Args...)> {}

function<int(float,bool)>; //1
function<int, float, bool>; //2

Option 1 est ce que vous voulez écrire, mais notez que vous passez un type de fonction unique à un modèle où les paramètres sont des paramètres de deux types et un paquet de paramètres de type. En d'autres termes, si vous écrivez ceci sans modèle primaire, vos arguments de modèle ne correspondront pas nécessairement aux paramètres du modèle. Option 2 correspond aux paramètres du modèle, mais pas à la spécialisation.

Cela a encore moins de sens si vous avez plus d'une spécialisation :

template <class RV, class Arg1, class... Args>
class function<RV(Arg1, Args...)> {}

template <class T, class RV, class Arg1, class... Args>
class function<RV (T::*) (Arg1, Args...)> {}

On pourrait peut-être imaginer des règles permettant de déduire le modèle principal à partir des spécialisations, mais cela me semble assez horrible.

8voto

Angew Points 53063

Vous devez garder à l'esprit que int (float, bool) est un type . Plus précisément, il s'agit du type "fonction qui prend deux paramètres de type float y bool et renvoie int ."

Puisqu'il s'agit d'un type, il est évident que le modèle doit avoir un paramètre de type à l'endroit où vous souhaitez utiliser la syntaxe int (float, bool) .

En même temps, le fait de n'avoir que le type de fonction est peu pratique. Bien sûr, on peut le faire facilement. Par exemple, si tout ce dont vous avez besoin est une sorte de transitaire :

template <class T>
struct CallForwarder
{
  std::function<T> forward(std::function<T> f)
  {
    std::cout << "Forwarding!\n";
    return f;
  }
};

Cependant, dès que l'on veut accéder aux "composants" du type de fonction, il faut pouvoir introduire des identificateurs pour ces composants. La façon la plus naturelle de le faire est la spécialisation partielle pour les types de fonction, comme vous l'avez fait dans votre question (et tout comme std::function fait).

6voto

Holt Points 6689

Vous pouvez effectivement le faire :

template <typename T>
class X { };

X<int(char)> x;

Et dans la définition de X vous pouvez créer un std::function<T> comme vous le feriez pour un std::function<int(char)> . Le problème ici est que vous ne pouvez pas (du moins facilement) accéder au type de retour et au type des paramètres de l'argument ( int y char ici).

En utilisant le "truc", vous pouvez y accéder sans problème - cela rend "simplement" votre code plus propre.

4voto

iammilind Points 29275

"Pourquoi dois-je donner au modèle une forme générale vide avec un paramètre de type vide ?

En effet, vous souhaitez une différenciation explicite des types entre le "type de retour" et les "types d'arguments", avec la même lucidité que le sucre syntaxique. En C++, aucune syntaxe de ce type n'est disponible pour la version de première main de la fonction template class . Il faut le spécialiser, le distinguer.

Après avoir lu votre question, j'ai consulté le <functional> fichier d'en-tête. Même std::function fait la même chose :

// <functional>  (header file taken from g++ Ubuntu)

template<typename _Signature>
class function;

// ...

 /**
   *  @brief Primary class template for std::function.
   *  @ingroup functors
   *
   *  Polymorphic function wrapper.
   */
template<typename _Res, typename... _ArgTypes>
class function<_Res(_ArgTypes...)>

Comme vous le voyez dans leurs commentaires, ils traitent la version spécialisée comme la classe "primaire". Cette astuce provient donc de l'en-tête standard lui-même !

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