david
david

Reputation: 205

C++ Template Class Constructor with Variable Arguments

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?

Upvotes: 11

Views: 17845

Answers (8)

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 506837

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 are homogeneous 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.

Upvotes: 2

der Fiese
der Fiese

Reputation: 1

The problem with variable arguments in constructors is :

  • you need the cdecl calling convention (or another one that can handle varargs)
  • you cant define cdecl for a constructor (in MSVS)

So the "correct" code (MS) could be :

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

but the compiler will say:

illegal calling convention for constructor/destructor (MS C4166)

Upvotes: 0

PersianGulf
PersianGulf

Reputation: 2935

You can use variadic , variadic means template with variable argument.more

Upvotes: 0

UncleBens
UncleBens

Reputation: 41333

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

Upvotes: 0

Andreas Brinck
Andreas Brinck

Reputation: 52519

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

Upvotes: 2

Michael Aaron Safyan
Michael Aaron Safyan

Reputation: 95449

In C++0x (really should be called C++1x), you can use template varargs to achieve what you want in a typesafe fashion (and you won't even need to specify the number of arguments!). However, in the current version of C++ (ISO C++ 1998 with 2003 amendments), there is no way to accomplish what you want. You can either hold off or do what Boost does, which is use preprocessor macro magic to repeat the definition of the constructor multiple times with different numbers of parameters up to a hard-coded, but large limit. Given that Boost.Preprocessor is kind of complicating, you could just define all of the following yourself:

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

Since the above is kind of painful to do by hand, though, you could write a script to generate it.

Upvotes: 0

swestrup
swestrup

Reputation: 4209

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.

Upvotes: 9

Related Questions