Arguments de modèle par défaut pour les modèles de fonction

187

Pourquoi les arguments de modèle par défaut sont-ils autorisés uniquement sur les modèles de classe? Pourquoi ne pouvons-nous pas définir un type par défaut dans un modèle de fonction membre? Par exemple:

struct mycclass {
  template<class T=int>
  void mymember(T* vec) {
    // ...
  }
};

Au lieu de cela, C ++ force que les arguments de modèle par défaut ne soient autorisés que sur un modèle de classe.

Arman
la source
8
+1 C'est vraiment une question délicate.
AraK
1
Pour les trois premières réponses publiées, considérez cet exemple: struct S { template <class R = int> R get_me_R() { return R(); } };Le paramètre de modèle ne peut pas être déduit du contexte.
AraK
3
Bonne question. 3 personnes ont déjà répondu pour dire que "cela n'a pas de sens", et elles se trompent toutes en général. Les paramètres de modèle de fonction ne sont pas toujours déductibles des paramètres d'appel de fonction. Par exemple, s'ils étaient autorisés, je pourrais écrire template <int N = 1> int &increment(int &i) { i += N; return i; }, puis increment(i);ou increment<2>(i);. En l'état, je dois écrire increment<1>(i);.
Steve Jessop
En fait, le mien et les exemples d'AraK peuvent tous deux être traités par surcharge. litb ne peut pas, je pense, parce que le paramètre de modèle peut être déduit ou spécifié.
Steve Jessop
3
@Steve: Le point-virgule manquant est en fait une nouvelle surcharge d'opérateur EOL pour compléter "Overloading of C ++ Whitespace" de B. Stavtrup publié dans Journal of Object-Oriented Programming, 1er avril 1992. ( www2.research.att.com/~bs/ papiers.html )

Réponses:

148

Il est logique de donner des arguments de modèle par défaut. Par exemple, vous pouvez créer une fonction de tri:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C ++ 0x les introduit au C ++. Voir ce rapport de défaut par Bjarne Stroustrup: Arguments de modèle par défaut pour les modèles de fonction et ce qu'il dit

L'interdiction des arguments de modèle par défaut pour les modèles de fonction est un vestige erroné de l'époque où les fonctions autonomes étaient traitées comme des citoyens de seconde classe et exigeaient que tous les arguments de modèle soient déduits des arguments de fonction plutôt que spécifiés.

La restriction gêne sérieusement le style de programmation en rendant inutilement les fonctions autonomes différentes des fonctions membres, rendant ainsi plus difficile l'écriture de code de style STL.

Johannes Schaub - litb
la source
@Arman, le lien du rapport d'anomalie contient les modifications apportées au brouillon de travail pour C ++ 0x et les discussions. Les arguments ni déduits ni explicitement spécifiés sont obtenus à partir des arguments par défaut. GCC4.4 prend en charge les arguments par défaut pour les modèles de fonctions en mode C ++ 0x.
Johannes Schaub - litb
4
Rien à voir avec la question ou la réponse, mais Herb Sutter a appelé la nouvelle norme C ++ 11 après la dernière réunion du samedi. Je viens de le lire aujourd'hui et j'ai envie de partager :) herbsutter.wordpress.com/2010/03/13/…
David Rodríguez - dribeas
et la question de suivi obligatoire ... quand est-ce que cela devrait faire son chemin dans d'autres compilateurs :)
Jamie Cook
@ JohannesSchaub-litb J'ai eu le même problème: aucune possibilité de préciser le type par défaut dans une fonction de modèle. J'ai résolu par une instanciation explicite de la fonction sur le type par défaut ( doubledans mon cas). Ce n'est peut-être pas «général», mais y a-t-il un inconvénient à cette pratique? Merci.
JackOLantern
Le code suivant ne parvient pas à se compiler, avec des erreurs comme error: invalid conversion from ‘int’ to ‘int*’, toute idée pourquoi: `#include <array> #include <algorithm> #include <functional> template <typename Iterator, typename Comp = std :: less <Iterator>> void my_sort ( Iterator beg, Iterator end, Comp c = Comp ()) {std :: sort (begin, end, c); } int main () {std :: array <int, 5> ar {5,2,21,7,4}; mon_sort (ar.begin (), ar.end ()); } `
Luke Peterson
36

Pour citer des modèles C ++: Le guide complet (page 207):

Lorsque les modèles ont été initialement ajoutés au langage C ++, les arguments de modèle de fonction explicites n'étaient pas une construction valide. Les arguments du modèle de fonction devaient toujours être déductibles de l'expression d'appel. En conséquence, il ne semblait y avoir aucune raison impérieuse d'autoriser les arguments de modèle de fonction par défaut, car la valeur par défaut serait toujours remplacée par la valeur déduite.

James McNellis
la source
simple and concise :)
InQusitive
17

Jusqu'à présent, tous les exemples proposés de paramètres de modèle par défaut pour les modèles de fonction peuvent être réalisés avec des surcharges.

AraK:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

pourrait être:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

Le mien:

template <int N = 1> int &increment(int &i) { i += N; return i; }

pourrait être:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

litb:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

pourrait être:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

Pourrait être:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

Ce que j'ai prouvé avec le code suivant:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

La sortie imprimée correspond aux commentaires pour chaque appel à f, et l'appel commenté ne parvient pas à se compiler comme prévu.

Je soupçonne donc que les paramètres de modèle par défaut "ne sont pas nécessaires", mais probablement seulement dans le même sens que les arguments de fonction par défaut "ne sont pas nécessaires". Comme l'indique le rapport de défaut de Stroustrup, l'ajout de paramètres non déduits était trop tard pour que quiconque se rende compte et / ou apprécie vraiment qu'il rendait les valeurs par défaut utiles. La situation actuelle est donc en fait basée sur une version de modèles de fonctions qui n'a jamais été standard.

Steve Jessop
la source
@Steve: L'œuf coulait plus vite que le poulet? :) intéressant. Merci.
Arman
1
Probablement juste une de ces choses. Le processus de normalisation C ++ est en partie lent, de sorte que les gens ont le temps de se rendre compte lorsqu'un changement crée des opportunités ou des difficultés ailleurs dans la norme. Espérons que les personnes qui mettent en œuvre le projet de norme attrapent les difficultés au fur et à mesure qu’elles découvrent une contradiction ou une ambiguïté. Opportunités d'autoriser des choses qui n'étaient pas autorisées auparavant, comptez sur quelqu'un qui veut écrire le code en remarquant que cela n'a plus besoin d'être illégal ...
Steve Jessop
2
Un plus pour vous: template<typename T = void> int SomeFunction();. Le paramètre template ici n'est jamais utilisé, et en fait la fonction n'est jamais appelée; le seul endroit auquel il est fait référence est dans un decltypeou sizeof. Le nom correspond délibérément au nom d'une autre fonction, mais le fait qu'il s'agisse d'un modèle signifie que le compilateur préférera la fonction gratuite si elle existe. Les deux sont utilisés dans SFINAE pour fournir un comportement par défaut lorsqu'une définition de fonction est manquante.
Tom
4

Sous Windows, avec toutes les versions de Visual Studio, vous pouvez convertir cette erreur ( C4519 ) en avertissement ou la désactiver comme suit :

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

Voir plus de détails ici .

Adi Shavit
la source
1
Notez que, bien que cela désactive le message «Les arguments de modèle par défaut ne sont autorisés que sur un modèle de classe», cela ne permet pas au processus d'instanciation de modèle d' utiliser la valeur fournie. Cela nécessite VS2013 (ou tout autre compilateur qui a terminé le défaut C ++ 11 226 «Arguments de modèle par défaut pour les modèles de fonction»)
puetzk
1

Ce que j'utilise est la prochaine astuce:

Disons que vous voulez avoir une fonction comme celle-ci:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

Vous ne serez pas autorisé, mais je le fais de la manière suivante:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

Donc, de cette façon, vous pouvez l'utiliser comme ceci:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

Comme nous pouvons le voir, il n'est pas nécessaire de définir explicitement le deuxième paramètre. Peut-être que ce sera utile pour quelqu'un.

alariq
la source
Ce n'est certainement pas une réponse.
Puppy
@deadmg - pouvez-vous expliquer pourquoi? Nous ne sommes pas tous des gourous du template C ++. Merci.
Kev
C'est une solution de contournement qui est assez soignée mais qui ne couvrira pas tous les cas que vous pourriez souhaiter. Par exemple, comment appliquez-vous cela à un constructeur?
Tiberiu Savin
@TiberiuSavin - si je vous ai bien compris, vous pouvez faire comme ceci: template <typename E, typename ARR_E> worker <E, ARR_E> :: worker (ARR_E * parr) {parr_ = parr; }. Et puis utilisez-le comme ceci: worker <int> w2 (& my_array);
alariq