Конструктор класса шаблона C ++ с переменными аргументами - PullRequest
10 голосов
/ 13 апреля 2010

Можно ли создать шаблонную функцию, которая принимает переменное число аргументов, например, в этом 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 s, а затем биты вставляются в double. Звонок Vector< double, 3 >( 1.0, 1.0, 1.0 ) дает желаемый результат. Есть ли предпочтительный способ сделать что-то подобное?

Ответы [ 8 ]

9 голосов
/ 13 апреля 2010

Увы, сейчас нет хорошего способа сделать это. Большинство пакетов 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) и предоставляют механизм для изменения максимального количества параметров перед расширением конструкции.

По сути, это реальная проблема, поэтому C ++ 0x вводит аргументы шаблона переменной длины и методы делегирования, которые позволят вам сделать это чисто (и безопасно). Тем временем вы можете сделать это с помощью макросов или попробовать компилятор C ++, который поддерживает (некоторые из) эти новые экспериментальные функции. GCC хорош для этого.

Имейте в виду, что поскольку C ++ 0x на самом деле еще не выпущен, все еще может измениться, и ваш код может не синхронизироваться с окончательной версией стандарта. Кроме того, даже после выхода стандарта в течение примерно пяти лет многие компиляторы будут поддерживать стандарт лишь частично, поэтому ваш код не будет очень переносимым.

2 голосов
/ 13 апреля 2010

Вы можете делать то, что хотите, но не делайте этого, потому что это небезопасно. Лучше всего передать вектор 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 достаточно места.

2 голосов
/ 13 апреля 2010

Этот код выглядит опасно, и я думаю, что ваш анализ того, почему он не работает, у компилятора нет возможности узнать об этом при вызове:

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);
0 голосов
/ 26 июля 2012

Проблема с переменными аргументами в конструкторах:

  • вам нужно соглашение о вызовах cdecl (или другое, которое может обрабатывать varargs)
  • вы не можете определить cdecl для конструктора (в MSVS)

Таким образом, «правильный» код (MS) может быть:

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

но компилятор скажет:

недопустимое соглашение о вызовах для конструктора / деструктора (MS C4166)

0 голосов
/ 08 июля 2012

Вы можете использовать шаблон с переменными значениями, с переменным аргументом.

0 голосов
/ 13 апреля 2010

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));
0 голосов
/ 13 апреля 2010

В C ++ 0x (на самом деле должен называться C ++ 1x), вы можете использовать шаблон varargs для достижения того, что вы хотите, в безопасном виде (и вам даже не нужно указывать количество аргументов!). Однако в текущей версии C ++ (ISO C ++ 1998 с поправками 2003 года) нет способа выполнить то, что вы хотите. Вы можете либо удерживать, либо делать то, что делает Boost, то есть использовать макро-магия препроцессора , чтобы повторить определение конструктора несколько раз с различным количеством параметров, вплоть до жестко заданного, но большого предела. Учитывая, что Boost.Preprocessor немного усложняет, вы можете просто определить все из следующего:

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

Поскольку вышесказанное довольно болезненно делать вручную, вы можете написать скрипт для его генерации.

0 голосов
/ 13 апреля 2010
...