2010-04-13 12 views
10

Ist es möglich, eine Template-Funktion zu erstellen, die eine variable Anzahl von Argumenten, zum Beispiel nimmt, in diesem Vector< T, C > Klassenkonstruktors:C++ Template-Klasse Constructor mit Variable Argumenten

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); 
} 

Diese fast funktioniert, aber wenn jemand anruft Vector< double, 3 >(1, 1, 1), nur das erste Argument hat den korrekten Wert. Ich vermute, dass der erste Parameter korrekt ist, weil er während des Funktionsaufrufs in eine double umgewandelt wird und dass die anderen als int interpretiert werden und dann die Bits in eine double gestopft werden. Der Aufruf Vector< double, 3 >(1.0, 1.0, 1.0) gibt die gewünschten Ergebnisse. Gibt es einen bevorzugten Weg, so etwas zu tun?

+2

Beachten Sie, dass universelle initializer Syntax ++ 11 des C werden Sie dies in einer sicheren Weise geben. – sbi

Antwort

2

Dieser Code sieht gefährlich aus und ich denke, Ihre Analyse auf, warum es nicht funktioniert genau das Richtige ist, gibt es keine Möglichkeit für den Compiler zu wissen, dass beim Aufruf:

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

diejenigen sollten als verdoppelt weitergegeben werden .

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

statt und der Benutzer die Argumente als Array übergeben haben:

würde ich den Konstruktor so etwas wie ändern. Eine andere Art von hässlichen Lösung wäre so etwas wie dieses:

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

und es so nennen (mit einigen typedefs):

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

Oder 'T (& Daten) [C]' und lassen Sie den Benutzer die Argumente als ein Array durch Verweis übergeben. Es ist weniger flexibel, da dynamische Arrays nicht verwendet werden können, es kann jedoch die korrekte Anzahl von Argumenten erzwingen. –

+0

@Chris Guter Punkt –

+2

Auch, mit Ihrem zweiten Vorschlag, stellen Sie sicher, dass Sie die Vorlage für "C = 0" und/oder "C = 1" spezialisieren. –

0

In C++ 0x (wirklich C++ 1x aufgerufen werden soll), können Sie Vorlage verwenden varargs zu erreichen, was Sie in einem typsicher wollen Mode (und Sie müssen nicht einmal die Anzahl der Argumente angeben!). In der aktuellen Version von C++ (ISO C++ 1998 mit Änderungen von 2003) gibt es jedoch keine Möglichkeit, das zu erreichen, was Sie wollen. Sie können entweder halten oder tun, was Boost tut, das ist preprocessor macro magic zu verwenden, um die Definition des Konstruktors mehrmals mit verschiedenen Anzahlen von Parametern bis zu einer fest programmierten, aber großen Grenze zu wiederholen. Da Boost.Preprocessor ist eine Art zu verkomplizieren, könnten Sie einfach alle die definieren sich folgende:

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

Da der oben genannten Art schmerzlicher ist von Hand zu tun, aber man könnte ein Skript schreiben, um es zu erzeugen.

+2

Jeder weiß, dass das "x" eine hexadezimale Ziffer ist ;-) –

9

Ach, jetzt gibt es kein guter Weg, dies zu tun. Die meisten der Boost-Pakete, die etwas ähnliches verwenden Makro Tricks tun müssen, Dinge wie diese zu definieren:

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) 
{ ... } 

Die Makros erzeugen eine festgelegte Anzahl (in der Regel etwa 10) Versionen, und einen Mechanismus bereitstellen, die maximale Anzahl der ändern Parameter vor der Erweiterung der Konstruktion.

Im Grunde genommen ist es ein echter Schmerz, weshalb C++ 0x Template-Argumente variabler Länge und Delegierungsmethoden einführt, mit denen Sie das sauber (und sicher) machen können. In der Zwischenzeit können Sie dies entweder mit Makros tun oder einen C++ - Compiler ausprobieren, der Unterstützung für einige dieser neuen experimentellen Funktionen bietet. GCC ist dafür eine gute Sache.

Seien Sie gewarnt, dass, da C++ 0x noch nicht aktiv ist, sich die Dinge noch ändern können und Ihr Code möglicherweise nicht mit der endgültigen Version des Standards übereinstimmt. Selbst nach dem Erscheinen des Standards wird es noch etwa 5 Jahre dauern, in denen viele Compiler den Standard nur teilweise unterstützen. Ihr Code wird also nicht sehr portabel sein.

+0

Überprüfen Sie Boost.Preprocessor, wenn Sie den Makroweg hinuntergehen. Das Kumulieren von 'BOOST_PP_REPEAT' und' BOOST_PP_ENUM_TRAILING_PARAMS' sollte Sie auf den richtigen Pfad bringen. –

+0

Danke. Ich hatte es eilig, als ich das Obige gepostet habe, also habe ich nicht nachgeschlagen, was die Boost Preprocessor-Makros waren. – swestrup

2

Sie können tun, was Sie wollen, aber tun Sie es nicht, weil es nicht typsicher ist. Übergeben Sie am besten einen Vektor von T oder ein Paar von Iteratoren, die diese Werte enthalten.

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); 

Dies kann besser gelöst werden, da alle Elemente trotzdem homogen typisiert sind.

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); 

Natürlich müssen Sie es sicherstellen, dass genug Platz in m_data ist.

+0

Wenn wir bereits 'template ' haben und der Benutzer bereits ein 'int values ​​[] 'Array deklariert, ist es vielleicht nicht sicherer (Größe), wenn der Konstruktor' Vector ist < T, C > :: Vektor (T (& const) [C]) '? Abgesehen davon, dass Slices eines Arrays oder eines dynamischen Arrays zulässig sind, gibt es einen Grund, warum wir ihnen nicht erlauben sollten, ein Array einfach zu passieren? –

+0

@Chris ja, wir könnten das tun. Ich fand heraus, dass ich den Iterator-Bereich sehr gerne zeige, da es der "offizielle" Weg ist, der auch von Containern verwendet wird. Sie können auch 'Vector v (Anfang (Werte), Ende (Werte)); Zählen Sie die Elemente selbst, mit genau definierten "end" - und "begin" -Funktionen (ab boost.range) –

0

std::tr1::array (die ähnlich wie bei Ihnen aussieht) definieren keinen Konstruktor, und kann als ein Aggregat initialisiert werden (?)

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

auch Sie Boost.Assignment Bibliothek überprüfen konnte.

Zum Beispiel könnte der Konstruktor

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

und Instanzen erstellt mit

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

Sie können variadische verwenden, bedeutet variadische Vorlage mit variabler Argument. more

0

Das Problem mit variablen Argumenten in Konstrukteuren ist:

  • müssen Sie die cdecl Konvention (oder ein anderes, das varargs umgehen kann) Aufruf
  • Sie
  • cdecl für einen Konstruktor (in MSVS) definieren kippen

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

So ist die "richtige" Code (MS) könnte sein,

aber der Compiler sagen:

illegal Aufrufkonvention für Konstruktor/Destruktor (MS C4166)