Конструктор класса шаблона С++ с переменными аргументами

Можно ли создать функцию шаблона, которая принимает переменное количество аргументов, например, в этом конструкторе класса 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 достаточно места.

Ответ 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)