2010-04-13 6 views
10

Czy to możliwe, aby utworzyć funkcję szablonu, że trwa zmienną liczbę argumentów, na przykład, w tym konstruktora Vector< T, C > klasy:C++ szablonu klasy Konstruktor ze zmiennym Argumenty

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

To prawie działa, ale jeśli ktoś dzwoni Vector< double, 3 >(1, 1, 1), tylko pierwszy argument ma poprawną wartość. Podejrzewam, że pierwszy parametr jest poprawny, ponieważ jest on rzutowany na double podczas wywołania funkcji, a pozostałe są interpretowane jako int s, a następnie bity są wpychane do double. Wywołanie Vector< double, 3 >(1.0, 1.0, 1.0) daje oczekiwane rezultaty. Czy istnieje preferowany sposób na zrobienie czegoś takiego?

+2

Zauważ, że uniwersalny składni C++ 11 za inicjatora dasz to w bezpieczny sposób. – sbi

Odpowiedz

2

Ten kod wygląda niebezpieczne i myślę, że analiza, dlaczego to nie działa to na miejscu, nie ma mowy o kompilator wiedzieć, że podczas wywoływania:

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

te powinny być przekazywane w deblu .

chciałbym zmienić konstruktora do czegoś podobnego:

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

zamiast, a użytkownik ma przekazać argumenty w postaci tablicy. Innym rodzajem brzydkie rozwiązanie byłoby coś takiego:

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

i nazywają to tak (z pewnymi typedefs):

Vector3(Vector2(Vector1(1), 1), 1); 
+2

Lub 'T (& data) [C]' i niech użytkownik przekazuje argumenty jako tablicę przez odniesienie. Jest mniej elastyczny, ponieważ nie pozwala na użycie tablic dynamicznych, ale może pomóc w egzekwowaniu prawidłowej liczby argumentów. –

+0

@Chris Dobry punkt –

+2

Ponadto, z drugą sugestią, upewnij się, że specjalizuje się szablon dla 'C = 0' i/lub' C = 1'. –

0

w C++ 0x (naprawdę powinien być nazywany C++ 1x), można użyć szablonu varargs aby osiągnąć to, co chcesz w typesafe moda (i nie musisz nawet określać liczby argumentów!). Jednak w obecnej wersji C++ (ISO C++ 1998 z poprawkami z 2003 roku), nie ma sposobu, aby osiągnąć to, co chcesz. Możesz albo wstrzymać działanie, albo zrobić to, co robi Boost, czyli użyć preprocessor macro magic, aby powtórzyć definicję konstruktora wiele razy z różnymi liczbami parametrów aż do sztywnego, ale dużego limitu. Biorąc pod uwagę, że Boost.Preprocessor jest rodzajem komplikuje, można po prostu określić wszystkie poniższe siebie:

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

Ponieważ powyżej jest trochę bolesne zrobić ręcznie, choć można napisać skrypt, który generuje.

+2

Każdy wie, że "x" jest cyfrą heksadecymalną ;-) –

9

Niestety, w tej chwili nie ma dobry sposób, aby to zrobić. Większość pakietów Boost trzeba zrobić coś podobnego makro sztuczki, aby określić takie rzeczy:

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

Makra wygenerować zestaw numer (zazwyczaj około 10) wersje i stworzyć mechanizm, aby zmienić maksymalną liczbę parametry przed rozszerzeniem konstrukcji.

Zasadniczo jest to prawdziwy ból, dlatego C++ 0x wprowadza argumenty o zmiennej długości i metody delegowania, które pozwolą ci to zrobić czysto (i bezpiecznie). W międzyczasie możesz to zrobić za pomocą makr lub wypróbować kompilator C++, który obsługuje (niektóre) z tych nowych funkcji eksperymentalnych. GCC jest dobre do tego.

Należy pamiętać, że ponieważ C++ 0x jeszcze się nie zakończyło, sytuacja wciąż może się zmienić, a kod może nie być zsynchronizowany z ostateczną wersją standardu. Dodatkowo, nawet po wyjściu standardu, będzie około 5 lat, podczas których wiele kompilatorów będzie tylko częściowo obsługiwać standard, więc twój kod nie będzie zbyt przenośny.

+0

Sprawdź Boost.Preprocessor, jeśli przejdziesz w makro. Łączenie "BOOST_PP_REPEAT" i "BOOST_PP_ENUM_TRAILING_PARAMS" powinno ustawić właściwą ścieżkę. –

+0

Dzięki. Spieszyłem się, kiedy opublikowałem powyższe informacje, więc nie sprawdziłem, jakie są makra Boost Preprocessor. – swestrup

2

Możesz robić, co chcesz, ale nie rób tego, ponieważ nie jest to bezpieczne. Najlepiej przekaż wektor T lub parę iteratorów zawierających te wartości.

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

Można to lepiej rozwiązać, ponieważ wszystkie elementy są mimo to homogeniczne.

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

Oczywiście, trzeba upewnić się, że jest wystarczająco dużo miejsca w m_data.

+0

Jeśli mamy już 'szablon ' i już robimy, że użytkownik deklaruje tablicę 'int values ​​[]', może nie być bezpieczniej (pod względem rozmiaru), aby konstruktor był 'Vector < T, C > :: Wektor (T (& const) [C]) '? Poza dopuszczeniem wycinków tablicy lub tablicy dynamicznej, czy jest jakiś powód, dla którego nie powinniśmy pozwolić im po prostu przekazać tablicy? –

+0

@ Chrześcijaństwo, możemy to zrobić. Zauważyłem, że chciałem pokazać zakres iteratorów, ponieważ jest to "oficjalny" sposób używany również przez kontenery itp. Możesz również zrobić 'Vector v (begin (wartości), end (wartości));' aby nie musieć liczyć elementy samodzielnie, z prawidłowo zdefiniowanymi funkcjami 'end' i' begin' (z boost.range) –

0

std::tr1::array (która wygląda podobnie do Ciebie) nie określa konstruktor i może być inicjowany jako kruszywo (?)

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

Również można sprawdzić Boost.Assignment bibliotekę.

Na przykład konstruktor może być

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

i instancje stworzone z

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

Można użyć zmiennej liczbie argumentów, o zmiennej liczbie argumentów oznacza szablonu o zmiennej argumentu. more

0

Problem ze zmiennymi argumenty konstruktorów jest:

  • potrzebujesz cdecl wzywającą Konwencji (lub inny, który może obsłużyć varargs)
  • nie można zdefiniować cdecl dla konstruktora (w MSVS)

więc "poprawny" kod (MS) mogą być:

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

ale kompilator powie:

nielegalna konwencja powołaniem do konstruktora/destruktora (MS C4166)