views:

705

answers:

6

Is it possible to create a template function that takes a variable number of arguments, for example, in this Vector< T, C > class constructor:

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

This almost works, but if someone calls Vector< double, 3 >( 1, 1, 1 ), only the first argument has the correct value. I suspect that the first parameter is correct because it is cast to a double during the function call, and that the others are interpreted as ints and then the bits are stuffed into a double. Calling Vector< double, 3 >( 1.0, 1.0, 1.0 ) gives the desired results. Is there a preferred way to do something like this?

A: 
Michael Aaron Safyan
Everyone knows that the "x" is a hexadecimal digit ;-)
MadKeithV
+1  A: 

This code looks dangerous and I think your analysis on why it isn't working is spot on, there's no way for the compiler to know that when calling:

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

the ones should be passed as doubles.

I would change the constructor to something like:

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

instead, and have the user pass the arguments as an array. Another sort of ugly solution would be something like this:

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

and call it like this (with some typedefs):

Vector3(Vector2(Vector1(1), 1), 1);
Andreas Brinck
Chris Lutz
@Chris Good point
Andreas Brinck
Also, with your second suggestion, make sure to specialize the template for `C = 0` and/or `C = 1`.
Chris Lutz
+6  A: 

Alas, right now there's no good way to do this. Most of the Boost packages that need to do something similar use macro tricks to define things like this:

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

The macros generate some set number (typically around 10) versions, and provide a mechanism to change the max number of parameters before expanding the construction.

Basically, its a real pain which is why C++0x is introducing variable-length template arguments and delegation methods that will let you do this cleanly (and safely). In the meantime you can either do it with macros, or try a C++ compiler that has support for (some of) these new experimental features. GCC is a good one for this.

Be warned though that since C++0x isn't actually out yet, things can still change and your code may not be in sync with the final version of the standard. Plus, even after the standard comes out, there'll be 5 years or so during which many compilers will only partially support the standard, so your code won't be very portable.

swestrup
Check out Boost.Preprocessor if you go down the macro way. Cumulating `BOOST_PP_REPEAT` and `BOOST_PP_ENUM_TRAILING_PARAMS` should set you on the right path.
Matthieu M.
Thanks. I was in a hurry when I posted the above, so I didn't go look up what the Boost Preprocessor macros were.
swestrup
+1  A: 

You can do what you want, but don't do it, because it's not typesafe. Best pass a vector of T or a pair of iterators containing those values.

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

This can be better solved, since all the elements ae homogenuous typed anyway

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

Of course, you have to make sure there is enough place in m_data.

Johannes Schaub - litb
Chris Lutz
@Chris yeah we could do that. I found i liked to show the iterator range way though, since it's the "official" way also used by containers etc. You can also do `Vector<int> v(begin(values), end(values));` to not have to count the elements yourself, with properly defined `end` and `begin` functions (from boost.range)
Johannes Schaub - litb
A: 

std::tr1::array (which looks similar to yours) does not define a constructor, and can be initialized as an aggregate (?)

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

Also you could check out Boost.Assignment library.

For example the constructor could be

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

and instances created with

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