2010-04-13 24 views
10

¿Es posible crear una plantilla de función que toma un número variable de argumentos, por ejemplo, en este constructor Vector< T, C > clase:de plantillas C++ Clase constructor con argumentos 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); 
} 

Esto casi funciona, pero si alguien llama Vector< double, 3 >(1, 1, 1), solo el primer argumento tiene el valor correcto. Sospecho que el primer parámetro es correcto porque se emite a double durante la llamada a la función, y que los otros se interpretan como int sy luego los bits se rellenan en un double. Llamar al Vector< double, 3 >(1.0, 1.0, 1.0) da los resultados deseados. ¿Hay una manera preferida de hacer algo como esto?

+2

Tenga en cuenta que la sintaxis de inicializador universales de C++ 11 le dará esto de una manera segura. – sbi

Respuesta

2

Este código es peligroso y creo que su análisis sobre por qué no está funcionando en el clavo, no hay manera para que el compilador sabe que cuando se llama:

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

los debe pasar como dobles .

que cambiaría el constructor para algo como:

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

lugar, y que el usuario pasa los argumentos como una matriz. Otro tipo de solución fea sería algo como esto:

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

y lo llaman así (con algunas typedefs):

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

O 'T (y datos) [C]' y hacer que el usuario pase los argumentos como una matriz por referencia. Es menos flexible ya que no permite el uso de matrices dinámicas, pero puede ayudar a aplicar la cantidad correcta de argumentos. –

+0

@Chris Punto bueno –

+2

Además, con su segunda sugerencia, asegúrese de especializar la plantilla para 'C = 0' y/o' C = 1'. –

0

En C++ 0x (en realidad debería llamarse C++ 1x), se puede utilizar la plantilla varargs para lograr lo que quieres en un typesafe moda (¡y ni siquiera necesitarás especificar la cantidad de argumentos!). Sin embargo, en la versión actual de C++ (ISO C++ 1998 con enmiendas de 2003), no hay forma de lograr lo que desea. Puede retener o hacer lo que hace Boost, que es usar preprocessor macro magic para repetir la definición del constructor varias veces con diferentes números de parámetros hasta un límite codificado, pero grande. Teniendo en cuenta que es una especie de Boost.Preprocessor complicando, sólo podría definir todos los siguientes ti mismo:

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

Dado lo anterior es una especie de dolorosa de hacer a mano, sin embargo, se podría escribir un script para generarlo.

+2

Todo el mundo sabe que la "x" es un dígito hexadecimal ;-) –

9

Por desgracia, en este momento no hay una buena manera de hacer esto. La mayoría de los paquetes de impulso que necesitan para hacer algo uso similar trucos macro para definir cosas como esta:

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

Las macros generan algún número fijo (típicamente alrededor de 10) versiones, y proporcionar un mecanismo para cambiar el número máximo de parámetros antes de expandir la construcción.

Básicamente, es un verdadero dolor de cabeza por lo que C++ 0x es la introducción de argumentos de plantilla de longitud variable y métodos de delegación que le permiten hacer esto de forma inadecuada (y segura). Mientras tanto, puedes hacerlo con macros, o probar un compilador de C++ que tenga soporte para (algunas de) estas nuevas funciones experimentales. GCC es bueno para esto.

Se advierte sin embargo que desde C++ 0x no es realmente a cabo, sin embargo, las cosas pueden cambiar y todavía su código no puede estar en sintonía con la versión final de la norma. Además, incluso después de que salga el estándar, habrá 5 años más o menos durante los cuales muchos compiladores solo admitirán parcialmente el estándar, por lo que su código no será muy portátil.

+0

Revisa Boost.Preprocessor si bajas por el camino macro. La acumulación de 'BOOST_PP_REPEAT' y' BOOST_PP_ENUM_TRAILING_PARAMS' debe establecerlo en el camino correcto. –

+0

Gracias. Tenía mucha prisa cuando publiqué lo anterior, así que no busqué las macros del Preprocesador Boost. – swestrup

2

Puede hacer lo que quiera, pero no lo haga, porque no es seguro. Mejor pase un vector de T o un par de iteradores que contengan esos valores.

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

Esto puede ser mejor resuelto, ya que todos los elementos son homogéneos mecanografiadas de todos modos.

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

Por supuesto, usted tiene que asegurarse de que hay suficiente lugar en m_data.

+0

Si ya tenemos 'template ' y ya estamos haciendo que el usuario declare una matriz 'int values ​​[]', ¿no sería más seguro (tamaño-sabio) que el constructor sea 'Vector < T, C > :: Vector (T (& const) [C]) '? Además de permitir segmentos de una matriz o una matriz dinámica, ¿hay alguna razón por la que no deberíamos permitirles pasar una matriz? –

+0

@Chris, sí, podríamos hacer eso. Sin embargo, creo que me gusta mostrar el rango del iterador, ya que es el modo "oficial" que también usan los contenedores, etc. También puedes hacer 'Vector v (begin (values), end (values));' para no tener que cuente los elementos usted mismo, con las funciones 'end' y' begin' definidas correctamente (de boost.range) –

0

std::tr1::array (que tiene una apariencia similar a la suya) no define un constructor, y se puede inicializar como un agregado (?)

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

También se puede consultar Boost.Assignment biblioteca.

Por ejemplo el constructor podría ser

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

y las instancias creadas con

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

Puede utilizar variadic, variadic significa plantilla con argumentos variables. more

0

El problema con argumentos variables en constructores es:

  • necesita el cdecl convención de llamada (u otro que pueda manejar varargs)
  • usted no puede definir cdecl de un constructor (en MSVS)

Así que el código "correcta" (EM) podrían ser:

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

pero el compilador dirá:

convención de llamada ilegal que el constructor/destructor (MS C4166)

Cuestiones relacionadas