2010-04-13 8 views
10

Est-il possible de créer une fonction de modèle qui prend un nombre variable d'arguments, par exemple, dans ce constructeur de la classe Vector< T, C >:C++ Modèle Constructeur de la classe avec des arguments variables

template < typename T, uint C > 
Vector< T, C >::Vector(T, ...) 
{ 
    va_list arg_list; 
    va_start(arg_list, C); 
    for(uint i = 0; i < C; i++) { 
     m_data[ i ] = va_arg(arg_list, T); 
    } 
    va_end(arg_list); 
} 

Ce presque fonctionne, mais si quelqu'un appelle Vector< double, 3 >(1, 1, 1), seul le premier argument a la valeur correcte. Je soupçonne que le premier paramètre est correct car il est converti en double pendant l'appel de fonction, et que les autres sont interprétés comme int s, puis les bits sont placés dans un double. L'appel Vector< double, 3 >(1.0, 1.0, 1.0) donne les résultats souhaités. Y a-t-il un moyen préféré de faire quelque chose comme ça?

+2

Notez que la syntaxe initialiseur universelle de C++ 11 vous donnera cela d'une manière sûre. – sbi

Répondre

2

Ce code semble dangereux et je pense que votre analyse sur la raison pour laquelle il ne fonctionne pas est sur place, il n'y a aucun moyen pour le compilateur de savoir que lorsque vous appelez:

Vector< double, 3 >(1, 1, 1) 

ceux devrait être passé en double .

je changerais le constructeur à quelque chose comme:

Vector< T, C >::Vector(const T(&data)[C]) 

à la place, et que l'utilisateur passe les arguments comme un tableau. Une autre sorte de solution laide serait quelque chose comme ceci:

template < typename T, uint C > 
Vector< T, C >::Vector(const Vector<T, C - 1>& elements, T extra) { 
} 

et l'appeler comme celui-ci (avec quelques typedefs):

Vector3(Vector2(Vector1(1), 1), 1); 
+2

Ou 'T (& data) [C]' et que l'utilisateur passe les arguments comme un tableau par référence. Il est moins flexible car il ne permet pas l'utilisation de tableaux dynamiques, mais il peut aider à appliquer le nombre correct d'arguments. –

+0

@Chris Bon point –

+2

Aussi, avec votre deuxième suggestion, assurez-vous de spécialiser le modèle pour 'C = 0' et/ou' C = 1'. –

0

En C++ 0x (devrait vraiment être appelé C++ 1x), vous pouvez utiliser le modèle varargs pour obtenir ce que vous voulez dans un typesafe mode (et vous n'aurez même pas besoin de spécifier le nombre d'arguments!). Cependant, dans la version actuelle de C++ (ISO C++ 1998 avec modifications de 2003), il n'y a aucun moyen d'accomplir ce que vous voulez. Vous pouvez soit attendre ou faire ce que fait Boost, qui est d'utiliser preprocessor macro magic pour répéter la définition du constructeur plusieurs fois avec différents nombres de paramètres jusqu'à une limite codée en dur, mais grande. Étant donné que Boost.Preprocessor est une sorte de compliquait, vous pouvez définir simplement vous-même tous les éléments suivants:

 
Vector<T,C>::Vector(); 
Vector<T,C>::Vector(const T&); 
Vector<T,C>::Vector(const T&, const T&); 
// ... 

Depuis ce qui précède est de nature pénible à faire à la main, bien que, vous pouvez écrire un script pour générer.

+2

Tout le monde sait que le "x" est un chiffre hexadécimal ;-) –

9

Hélas, en ce moment il n'y a pas de bonne façon de le faire. La plupart des forfaits Boost qui ont besoin de faire quelque chose utilisation similaire astuces macro pour définir des choses comme ceci:

template < typename T > 
Vector<T>::Vector(T) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1, C c2) 
{ ... } 

template < typename T, uint C > 
Vector< T, C >::Vector(T t, C c1, C c2, C c3) 
{ ... } 

Les macros génèrent un certain nombre défini (généralement autour de 10) versions, et de fournir un mécanisme pour changer le nombre maximum de paramètres avant d'étendre la construction.

Fondamentalement, c'est une vraie douleur, c'est pourquoi C++ 0x introduit des arguments de modèle de longueur variable et des méthodes de délégation qui vous permettront de le faire proprement (et en toute sécurité). En attendant, vous pouvez soit le faire avec des macros, soit essayer un compilateur C++ qui supporte (certaines de) ces nouvelles fonctionnalités expérimentales. GCC est un bon pour cela. Notez cependant que puisque C++ 0x n'est pas encore sorti, les choses peuvent encore changer et votre code peut ne pas être synchronisé avec la version finale de la norme. De plus, même après la sortie de la norme, il y aura environ 5 ans au cours desquels de nombreux compilateurs ne supporteront que partiellement la norme, de sorte que votre code ne sera pas très portable.

+0

Vérifiez Boost.Preprocessor si vous descendez la macro. Cumuler 'BOOST_PP_REPEAT' et' BOOST_PP_ENUM_TRAILING_PARAMS' devrait vous mettre sur le bon chemin. –

+0

Merci. J'étais pressé quand j'ai posté ce qui précède, donc je ne suis pas allé voir ce que les macros du préprocesseur Boost étaient. – swestrup

2

Vous pouvez faire ce que vous voulez, mais ne le faites pas, car ce n'est pas de type sécurisé. Meilleur passe un vecteur de T ou une paire d'itérateurs contenant ces valeurs.

template < typename T, uint C > 
Vector< T, C >::Vector(int N, ...) 
{ 
    assert(N < C && "Overflow!"); 
    va_list arg_list; 
    va_start(arg_list, N); 
    for(uint i = 0; i < N; i++) { 
     m_data[i] = va_arg(arg_list, T); 
    } 
    va_end(arg_list); 
} 

Vector<int> v(3, 1, 2, 3); 

Ceci peut être mieux résolu, puisque tous les éléments sont de toute façon typés de manière homogène.

template < typename Iter, uint C > 
Vector< T, C >::Vector(Iter begin, Iter end) 
{ 
    T *data = m_data; 
    while(begin != end) 
     *data++ = *begin++; 
} 

int values[] = { 1, 2, 3 }; 
Vector<int> v(values, values + 3); 

Bien sûr, vous devez vous assurer qu'il ya assez de place dans m_data.

+0

Si nous avons déjà 'template ' et que nous faisons déjà déclarer à l'utilisateur un tableau 'int values ​​[]', ne serait-il pas plus sûr (taille) que le constructeur soit 'Vector < T, C > :: Vecteur (T (& const) [C]) '? En dehors de l'utilisation de tranches d'un tableau ou d'un tableau dynamique, y a-t-il une raison pour laquelle nous ne devrions pas leur permettre de simplement passer un tableau? –

+0

@Chris oui nous pourrions le faire. J'ai trouvé que j'aimais bien montrer la voie de l'itérateur, puisque c'est la manière "officielle" aussi utilisée par les conteneurs etc. Vous pouvez aussi faire 'Vector v (begin (values), end (valeurs));' ne pas avoir à comptez les éléments vous-même, avec les fonctions 'end' et' begin' correctement définies (à partir de boost.range) –

0

std::tr1::array (qui ressemble à la vôtre) ne définit pas un constructeur, et peut être initialisé comme un agrégat (?)

std::tr1::array<int, 10> arr = {{ 1, 2, 3, 4, 5, 6 }}; 

Vous pouvez également consulter la bibliothèque Boost.Assignment.

Par exemple, le constructeur pourrait être

template < typename T, uint C > 
template < typename Range > 
Vector< T, C >::Vector(const Range& r) 

et les instances créées avec

Vector<int, 4> vec(boost::assign::cref_list_of<4>(1)(3)(4)(7)); 
0

Vous pouvez utiliser variadique, variadique signifie modèle avec l'argument variable. more

0

Le problème avec des arguments variables dans les constructeurs est:

  • vous avez besoin de la convention d'appel cdecl (ou un autre qui peut gérer varargs)
  • vous ne pouvez pas définir cdecl pour un constructeur (en VSM)

Ainsi pourrait être le code "correct" (MS):

template < typename T, uint C > __cdecl Vector< T, C >::Vector(T, ...) 

mais le compilateur dira:

convention d'appel illégal constructeur/destructor (MS C4166)

Questions connexes