Конструктор класса шаблона С++ с переменными аргументами
Можно ли создать функцию шаблона, которая принимает переменное количество аргументов, например, в этом конструкторе класса Vector< T, C >
:
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 );
}
Это почти работает, но если кто-то называет Vector< double, 3 >( 1, 1, 1 )
, то только первый аргумент имеет правильное значение. Я подозреваю, что первый параметр является правильным, потому что он выполняется в double
во время вызова функции, а остальные интерпретируются как int
, а затем биты заполняются в double
. Вызов Vector< double, 3 >( 1.0, 1.0, 1.0 )
дает желаемые результаты. Есть ли предпочтительный способ сделать что-то вроде этого?
Ответы
Ответ 1
Этот код выглядит опасным, и я думаю, что ваш анализ на то, почему он не работает, на месте, нет никакого способа, чтобы компилятор знал, что при вызове:
Vector< double, 3 >( 1, 1, 1 )
те должны быть переданы как двойные.
Я бы сменил конструктор на что-то вроде:
Vector< T, C >::Vector(const T(&data)[C])
и попросите пользователя передать аргументы в виде массива. Другим видом уродливого решения было бы что-то вроде этого:
template < typename T, uint C >
Vector< T, C >::Vector(const Vector<T, C - 1>& elements, T extra) {
}
и назовите его так (с некоторыми typedefs):
Vector3(Vector2(Vector1(1), 1), 1);
Ответ 2
Увы, сейчас нет хорошего способа сделать это. Большинство пакетов Boost, которые должны сделать что-то подобное, используют макро-трюки для определения таких вещей:
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 )
{ ... }
Макросы генерируют некоторое количество заданных чисел (обычно около 10) и предоставляют механизм для изменения максимального количества параметров перед расширением конструкции.
В принципе, это настоящая боль, поэтому С++ 0x вводит аргументы шаблона переменной длины и методы делегирования, которые позволят вам сделать это чисто (и безопасно). Тем временем вы можете либо сделать это с помощью макросов, либо попробовать компилятор С++, который поддерживает (некоторые) эти новые экспериментальные функции. GCC является хорошим для этого.
Будем предупреждать, что, поскольку С++ 0x еще не наступил, все еще может измениться, и ваш код может не синхронизироваться с окончательной версией стандарта. Кроме того, даже после выхода стандарта будет около 5 лет, в течение которого многие компиляторы будут частично поддерживать стандарт, поэтому ваш код не будет очень переносимым.
Ответ 3
Вы можете делать то, что хотите, но не делайте этого, потому что он не набирает. Лучше всего передать вектор T
или пару итераторов, содержащих эти значения.
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);
Это может быть лучше решено, так как все элементы все равно однородны.
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);
Конечно, вы должны убедиться, что в m_data
достаточно места.
Ответ 4
Соответствует ли TypeList вашим потребностям?
Ответ 5
В С++ 0x (на самом деле нужно называть С++ 1x), вы можете использовать шаблоны varargs для достижения того, что вы хотите в режиме типа (и вам даже не нужно указывать количество аргументов!). Однако в текущей версии С++ (ISO С++ 1998 с поправками 2003 года) невозможно выполнить то, что вы хотите. Вы можете либо удержать, либо сделать то, что делает Boost, которое использует макрокоманду препроцессора, чтобы повторить определение конструктора несколько раз с разными номерами параметры до жестко закодированного, но большого предела. Учитывая, что Boost.Preprocessor является довольно сложным, вы можете просто определить все из следующих:
Vector<T,C>::Vector();
Vector<T,C>::Vector(const T&);
Vector<T,C>::Vector(const T&, const T&);
// ...
Так как вышеупомянутое очень болезненно делать вручную, вы можете написать script для его создания.
Ответ 6
std::tr1::array
(который похож на ваш) не определяет конструктор и может быть инициализирован как совокупность (?)
std::tr1::array<int, 10> arr = {{ 1, 2, 3, 4, 5, 6 }};
Также вы можете проверить библиотеку Boost.Assignment.
Например, конструктор может быть
template < typename T, uint C >
template < typename Range >
Vector< T, C >::Vector( const Range& r )
и экземпляры, созданные с помощью
Vector<int, 4> vec(boost::assign::cref_list_of<4>(1)(3)(4)(7));
Ответ 7
Вы можете использовать вариационный, вариационный шаблон с переменным аргументом. больше
Ответ 8
Проблема с переменными аргументами в конструкторах:
- вам нужно соглашение о вызове cdecl (или другое, которое может обрабатывать varargs)
- Вы не можете определить cdecl для конструктора (в MSVS)
Таким образом, "правильный" код (MS) может быть:
template < typename T, uint C > __cdecl Vector< T, C >::Vector( T, ... )
но компилятор скажет:
незаконное вызов для конструктора/деструктора (MS C4166)