48
Programowanie
C++
www.sdjournal.org
Software Developer’s Journal 05/2007
Variadic templates w C++0x
V
ariadic templates, czy też lekko karkołomnie
tłumacząc na polski, „wzorce ze zmienną li-
stą parametrów”, to kolejna z ciekawych wła-
ściwości, które mają się pojawić w nowym standar-
dzie C++0x. I, podobnie jak koncepcje, doczekała
się nawet próbnej implementacji. Zanim przedstawię,
jak owo rozwiązanie wygląda, zacznę od problemów,
którym owa właściwość ma zaradzić.
Problem pierwszy: printf i scanf
O funkcjach o zmiennej liście argumentów w języku
C każdy chyba słyszał (mówię „w języku C”, bo uży-
wanie tego w C++ oznacza równocześnie rezygna-
cję z niemal wszystkich zalet tego języka). Niezależ-
nie od faktycznej implementacji, najłatwiej sobie to
wyobrazić jako surowy obszar pamięci, w którym po
kolei umieszczane są obiekty podane w wywołaniu.
Owe '...' w sygnaturze funkcji oznaczają jedynie, że tu
lista argumentów się nie kończy, a argumenty poda-
ne za ostatnim jawnym będą umieszczane w pamię-
ci jeden za drugim. Nie mamy jednak żadnych wska-
zówek, jak i co z tej pamięci czytać, jeśli ich nie poda-
my w jakiś sposób wprost podczas wywołania. Makra
z
<stdarg.h>
ułatwiają jedynie ich zbieranie i dostar-
czają kompilatorowi więcej swobody w implementa-
cji – ale nic ponadto. W C++, na dodatek, nie można
w ten sposób przekazać typów innych, niż POD.
Weźmy taką, najbardziej znaną,
printf
. Tam, jak
wiemy, w napisie formatującym umieszcza się znacz-
niki, jak np. „%d”, które mają określać, jakie dane na-
leży pobrać z obszaru nieokreślonych argumentów
(w tym przypadku jest to jeden obiekt typu int). Nic jed-
nak nie stoi na przeszkodzie, żeby w to miejsce podać
np. wartość typu long double i mamy poważny problem.
Z funkcją
scanf
z kolei jest jeszcze gorzej. Tam bar-
dzo łatwo zapomnieć, że znacznikowi „%d” musi odpo-
wiadać argument „&n” (a nie „n”), bo
scanf
na tej pozy-
cji oczekuje wskaźnika do zmiennej typu int, a nie war-
tości typu int. Najlepsze fajerwerki zapewnia zaś kon-
strukcja
scanf( "%1s", &c )
, gdzie 'c' jest typu char –
dzięki nadpisaniu pamięci zaraz za zmienną potrafi za-
pewnić nawet nieodtwarzalność stosu wywołań.
Niektóre kompilatory – próbując tym problemom
częściowo zaradzić – wprowadzają rozszerzenia, któ-
re sprawdzają tę poprawność. Kompilator gcc na przy-
kład, dostarcza rozszerzenie w postaci
_ _ attribu-
te _ _ ((format))
, który informuje kompilator, że łańcuch
formatujący stosuje się do reguł określonej standardo-
wej funkcji (dostępne są
printf
,
scanf
,
strftime
i
strf-
mon
). W przypadku niepoprawnego wywołania kompila-
tor będzie stosował ostrzeżenie (np. o niewłaściwej ilo-
ści argumentów lub niewłaściwych typach). To jest jed-
nak, jak każdy wie, półśrodek (dla ciekawostki, powyż-
szego błędu w wywołaniu
scanf
to nie wykrywa).
Nie rozwodząc się nad tym dalej – funkcja nie ma
możliwości odgadnięcia, jakie argumenty zostały jej
przekazane, jakich one są typów, ani ile ich jest. I nie
bez powodu traktuje się ten ficzer jako naleciałość
z języka C, której nie należy stosować w C++ w ogóle.
Problem drugi – call forwarding
Każdy, kto zapoznał się z boost::function, boost::bind,
boost::signal, libsigc++, cpptcl, czy też jakąkolwiek inną
biblioteką, która posługuje się przekazywaniem wywo-
łań z jednej funkcji do drugiej (w postaci: „weź te argu-
menty i wywołaj podaną funkcję, przekazując jej te ar-
gumenty w identycznej postaci”), zauważył że dużo pa-
ry idzie w pewien specyficzny gwizdek, mianowicie do-
starczenie dziewięciu definicji do tego jednego najdrob-
niejszego szczegółu, który zajmuje się tym właśnie
przekazywaniem (no dobrze, boost::function dopusz-
cza nawet 50). Jest ich tyle, bo jedną definicję trzeba
dostarczyć do funkcji bez argumentów, jedną z jednym
argumentem, jedną z dwoma: (w którejś wersji boost::
function widziałem nawet skrypt perlowy, który genero-
wał wszystkie postacie z jednej definicji; obecnie boost:
:function używa ułatwień z Boost.Preprocessor, przez
co jest to zagmatwane jeszcze bardziej). Ile jest z tym
roboty, jakie są problemy z utrzymaniem takiego kodu
i ile bardziej on jest narażony na błędy – o tym chyba
nie trzeba nikomu przypominać.
Powielanie jednej definicji, z uwagi na konieczność
dostosowania do ilości argumentów, ma więcej wad,
niż sam fakt nadmiarowej pracy. Nie można jej przecież
powielać w nieskończoność. Wydaje się, że 9 powinno
wystarczyć, ale jak widzę niektóre funkcje z Windows
API, czy POSIX threads, to zaczynam mieć wątpliwo-
ści. Tak czy owak – pożądane jest rozwiązanie, dzięki
któremu będzie można załatwić tę sprawę jedną, pro-
stą definicją. W projekcie C++0x istnieje zatem wstęp-
nie zaakceptowana postać takiego rozwiązania.
Variadic templates
– najprościej mówiąc...
Autor poprawki do gcc, a zarazem też jeden z auto-
rów tego ficzera, Douglas Gregor, zamieścił na swo-
jej stronie (patrz ramka W Sieci) przykład funkcji
printf
, która używa swoich argumentów w bezpiecz-
Michał Małecki
Autor interesuje się psychologią, programowaniem, mu-
zyką, publicystyką i językoznawstwem, a w wolnych
chwilach pracuje jako programista.
Kontakt z autorem: ethouris@gmail.com
Variadic templates w C++0x
49
www.sdjournal.org
Software Developer’s Journal 05/2007
niejszy sposób (znaczniki określa się samym znakiem „%”,
a typy i ilość argumentów są funkcji znane). Ja zaś proponuję
coś jeszcze prostszego – patrz Listing 1. Funkcji tej używa się
w następujący sposób:
print( "Mam ", n, " lat.\n" );
print( "Pracuję na stanowisku ", stanowisko, " i zarabiam ",
get_secure( xkey, zarobki ), " dolców\n" );
Co ważne, taka postać print zachowuje wszystkie zalety io-
stream – operuje również obiektami, których typy nie są zna-
ne w momencie kompilacji funkcji print. Wyjaśnienie, jak dzia-
łają powyższe definicje, jest proste. Jeśli wywołamy print
z jednym argumentem, to zostanie użyta wersja jednoargu-
mentowa. Jeśli z dwoma, wtedy zostanie użyta wersja wielo-
argumentowa, ale wewnątrz wywoła jednoargumentową print,
a potem dokona wywołania wieloargumentowego. Ponieważ
jednak w tych wielokrotnych argumentach pozostał już tylko
jeden argument, to ta postać zostanie również przekierowa-
na do jednoargumentowej print. Jeśli argumentów jest więcej
– wersja wieloargumentowa będzie wywoływana dotąd, aż li-
sta argumentów stopnieje do jednego.
Doug Gregor, w ramach żartu, wymyślił jeszcze prostszą
postać (aczkolwiek ta właśnie wersja została wpisana do do-
kumentu z opracowaną propozycją dla komitetu standaryza-
cyjnego C++) – patrz Listing 2. Tutaj
eat
dostanie kolejne ar-
gumenty, z którymi nie robi nic, a cała akcja zostanie wykona-
na przez wyrażenie z std::cout.
Co to jest „typename ...” ?
Dobrze, wiem – przykład prosty, ale zrozumiale to on nie wy-
gląda. Fakt – reguły, jakimi się rządzą variadic templates nie są
specjalnie intuicyjne, ale dobre objaśnienie koncepcji variadic
templates – a takowe mam ambicję w tym artykule dostarczyć –
powinno wystarczyć do pełnego zrozumienia, z czym się to je.
Zacznijmy więc od początku. Konstrukcja „typename...”
jest tu najważniejsza, gdyż na tym właśnie zasadza się nowy
typ funkcji o zmiennej liście argumentów w C++. Konieczność
opierania się ich na wzorcach jest zresztą oczywista: sko-
ro nie wiemy, jakie argumenty zostaną przekazane do funk-
cji – zwłaszcza, że nie wiemy, ile ich będzie – to musimy zało-
żyć, że ich typ jest nieznany i powinien zostać wydedukowany,
a do tego wzorce właśnie służą.
Zatem konstrukcja „
typename... Args
” oznacza, że w rzeczy-
wistości będzie się to rozwijało jako „
typename Arg1, typename
Arg2, ...
” i tak dalej. Oczywiście cyfr 1 i 2 użyłem tu symbolicz-
nie, żeby można było łatwiej to sobie wyobrazić. Wspomniane
Args
będzie potem używane już w definicji wzorca.
Args
jest tu-
taj czymś w rodzaju „wielokrotnego parametru wzorca” i będzie
nam – odpowiednio – definiował również wielokrotny argument
funkcji (Doug określa to terminem argument pack).
A potem było „Args... args”, tak?
No właśnie. Pomińmy na razie postać, jaką pokazałem w przy-
kładzie – przyjmijmy na razie taką postać jak tu w tytule rozdzia-
łu, bo tak będzie wygodniej. Taka konstrukcja definiuje nam tzw.
argument wielokrotny, co oznacza, że tego '
args
' będzie można
następnie używać tak, jakby był po prostu zwykłym obiektem ty-
pu
'Args'
. Ponieważ jest on jednak obiektem wielokrotnym, więc
– jak się można domyślać – trzeba teraz określić, co mamy zro-
bić z tym obiektem, a co z jego tzw. „ogonem”, który jest ozna-
czany jako „...”. To nie jest prosta sprawa i dla mnie również z po-
czątku nie wyglądało to intuicyjnie. Jednak po dyskusji z Do-
ugiem Gregorem doszedłem do wniosku, że faktycznie takie
rozwiązanie jest najlepsze. Popatrzmy zatem na nasz pierwszy
przykład, a konkretnie linijkę, która używa
'args...'
:
print( args... );
Odnosząc się do wspomnianego
"template... Args"
, powyż-
sza konstrukcja rozwinie się jako:
print( arg1, arg2, arg3 ... itd );
Zaraz!
A skąd ten przecinek? I dlaczego tu?
Ano właśnie. Tu się mieści sedno sprawy. W istocie jest to
znacznie bardziej prostackie, niż się wydaje: rzeczywiście,
rozwijanie wielokropka polega na zastąpieniu go „listą argu-
Rysunek 1.
Schemat Variadic templates
���������������������������
��������������������
�
�����������������������
�
�����������������������������������������������
�
������������������������������������������������
�
�����������������������
��������������������
�
��������������
�
����������
�������������
���������
�����������������������
�����������������������
��������������
�
������������������������
��������������
��
��������������
�
������������������������������������
��
��������������������������
����������
�������������
������
Listing 1.
Przykładowa funkcja „print” z nieokreśloną
liczbą argumentów
template
<
typename
T
>
inline
void
(
const
T
&
t
)
{
std
::
cout
<<
t
;
}
template
<
typename
T
,
typename
...
Args
>
inline
void
(
const
T
&
t
,
const
Args
&
...
args
)
{
(
t
);
(
args
...
);
}
50
Programowanie
C++
www.sdjournal.org
Software Developer’s Journal 05/2007
Listing 3.
Fragment kodu napisany wedle reguł współczesnego C++
mentów”, to znaczy właśnie, na wstawieniu m.in. odpowied-
niej liczby przecinków. Drugie pytanie jednak okazuje się
nieco trudniejsze, choć odpowiedź jest bardzo prosta: prze-
cinek zostanie wstawiony tam, gdzie oryginalnie był wielo-
kropek. To gdzie jest w takim razie początek wyrażenia, któ-
re zostanie powtórzone? Otóż dokładnie tam, gdzie się za-
czyna wyrażenie zakończone wielokropkiem (podpowiedź:
początek wyrażenia nie może znajdować się przed otwar-
ciem nawiasu, który nie jest zamknięty przed końcem wyra-
żenia). A bardziej na żywca? No to wyobraźmy sobie, że ten
argument przed przekazaniem do
print()
musi zostać jesz-
cze przesiany przez funkcję
reformat()
. Czyli zamiast cze-
goś takiego:
print( arg1, arg2, arg3 );
chcemy mieć tak:
print( reformat( arg1 ), reformat( arg2 ), reformat( arg3 ) );
No to przecież bardzo proste:
print( reformat( args )... );
Tu proszę zauważyć jedną bardzo istotną rzecz: wszystkie argu-
menty z listy wielokrotnego argumentu muszą zostać przez wy-
rażenie, które go zawiera (!), potraktowane w dokładnie ten sam
sposób. Nie możemy bezpośrednio w takim wywołaniu np. użyć
innego sposobu traktowania argumentów parzystych i nieparzy-
stych. To też można oczywiście zrobić, zatrudniając do tego do-
datkową funkcję, która przyjmie co najmniej dwa argumenty (tyl-
ko trzeba dostarczyć wersję jednoargumentową, żeby zgłosić
błąd wywołania). No dobrze – zgodnie z powyższymi objaśnie-
niami, można teraz jasno powiedzieć, co dokładnie oznacza ar-
gument „
const Args&... args
”. Będzie rozwinięte na coś w ro-
dzaju „
const Arg1& arg1, const Arg2& arg2 ...
” itd.
To ja poproszę coś mocniejszego
Oczywiście variadic templates nie służą tylko do definiowa-
nia funkcji o zmiennej liście argumentów. Ponieważ jest to me-
chanizm dotyczący przede wszystkim wzorców, więc na po-
dobnej zasadzie można tworzyć wzorzec klasy. Stosuje się
Listing 2.
Prostsza postać funkcji „print”
template
<
typename
...
Args
>
inline
void
eat
(
const
Args
&
...
)
{}
template
<
typename
...
Args
>
void
(
const
Args
&
...
args
)
{
eat
(
std
::
cout
<<
args
...
);
}
//
Tu
przy
mniejszej
liczbie
ni
ż 6
reszta
jest
dope
ł
niana
"void"
// a zamiast function6 jest użyte odpowiednio function5 itd.
template
<
class
Type1
,
class
Type2
,
class
Type3
,
class
Type4
,
class
Type5
,
class
Type6
>
struct
HandlerType
{
typedef
boost
::
function6
<
bool
,
Type1
,
Type2
,
Type3
,
Type4
,
Type5
,
Type6
>
type
;
}
;
class
BasicEventDispatcher
{
public
:
virtual
bool
Dispatch
(
const
BasicEvent
*
args
)
const
=
0
;
virtual
~
BasicEventDispatcher
()
{}
}
;
template
<
class
EventArgs
,
class
Type1
,
class
Type2
,
class
Type3
,
class
Type4
,
class
Type5
,
class
Type6
>
class
EventDispatcher
:
public
BasicEventDispatcher
{
public
:
typedef
typename
HandlerType
<
typename
Type1
::
type
,
typename
Type2
::
type
,
typename
Type3
::
type
,
typename
Type4
::
type
,
typename
Type5
::
type
,
typename
Type6
::
type
>::
type
handler_t
;
handler_t
handler
;
EventDispatcher
(
handler_t
hnd
):
handler
(
hnd
)
{}
virtual
bool
Dispatch
(
const
BasicEvent
*
basic_args
)
const
{
const
EventArgs
*
args
=
static_cast
<
const
EventArgs
*>(
basic_args
);
return
DispatchEventInternal
(
args
,
handler
,
Type1
()
,
Type2
()
,
Type3
()
,
Type4
()
,
Type5
()
,
Type6
()
);
}
}
;
// Tu wzorzec ma tyle parametrów, ile argumentów funkcji
"handler",
template
<
class
EventArgs
,
class
Function
,
class
Type1
,
class
Type2
,
class
Type3
,
class
Type4
,
class
Type5
,
class
Type6
>
inline
// a w argumentach DispatchEventInternal przy mniejszej ich
liczbie
// zamiast Type6 itd. użyty jest specjalny typ UnusedArg
bool
DispatchEventInternal
(
const
EventArgs
*
args
,
Function
handler
,
Type1
,
Type2
,
Type3
,
Type4
,
Type5
,
Type6
)
{
return
handler
(
Shell
(
Type1
()
,
*
args
)
,
Shell
(
Type2
()
,
*
args
)
,
Shell
(
Type3
()
,
*
args
)
,
Shell
(
Type4
()
,
*
args
)
,
Shell
(
Type5
()
,
*
args
)
,
Shell
(
Type6
()
,
*
args
)
);
}
52
Programowanie
C++
www.sdjournal.org
Software Developer’s Journal 05/2007
to, co prawda, również do tego, żeby móc wewnątrz klasy za-
wrzeć mechanizmy pozwalające na obsługiwanie, za pomocą
jednej spójnej definicji, funkcji o dowolnej liczbie argumentów.
Przejdźmy zatem do bardziej „wypasionych” przykładów.
Kod, którego tu użyję, powstał na użytek pewnej bibliote-
ki, którą z trudem usiłuję stworzyć od pewnego czasu, a ze
względu na stopień komplikacji przy obsłudze funkcji o „do-
wolnej liczbie argumentów” dobrze się nadawał zarówno do
zaprezentowania możliwości variadic templates, jak i do do-
kładnego przetestowania łatki Douglasa Gregora do kompila-
tora gcc, która dodaje ową właściwość języka.
Na Listingu 3 przedstawiony został kod, który napisałem
pierwotnie, wedle obecnych reguł C++. Proszę zwrócić uwa-
gę na definicje pośredniczące, które musiałem stworzyć, że-
by wyodrębnić fragmenty odpowiedzialne za obsługę funkcji
z odpowiednią liczbą argumentów. Tu argumentów jest mak-
symalnie 6, co uznałem za dobry kompromis pomiędzy przy-
datnością dla użytkownika, a wrodzonym lenistwem. Z tych
zwielokrotnionych definicji podam oczywiście nie wszystkie,
a jedynie te z maksymalną liczbą parametrów – w komentarzu
podałem, jak są konstruowane pozostałe, a przytaczanie ich
tu byłoby niepotrzebnym marnowaniem miejsca.
Może najpierw jeszcze parę słów o przeznaczeniu tego
kodu. Jest to fragment biblioteki odpowiedzialnej za rejestro-
wanie i dispatchowanie zdarzeń. W zamierzeniu ma służyć ja-
ko pośrednik do rozsyłania zdarzeń GUI. Funkcjonalność re-
jestrowania zdarzeń nie jest tu zamieszczona (aż tyle nie po-
trzeba na użytek przykładu). Podpowiem tylko, że rejestrowa-
nie (funkcja
bind _ event
) tworzy nowy obiekt typu specjalizo-
wanego z
EventDispatcher
, a ten obiekt służy z kolei jako po-
średnik w wywołaniu handlera zdarzenia. Jest to tak złożone,
z tego względu, że funkcja rejestrująca ma mieć możliwość
podania jej funkcji o dowolnej liczbie argumentów (wiem, po-
wtarzam się), plus specjalnych znaczników określających, ja-
kie dane, przychodzące z tego zdarzenia, mają być przekazy-
wane do funkcji – na przykład tak:
bind_event( w, Right-Control-Button-1, &clicked_button, ea_
windid );
co oznacza, że rozsyłacz zdarzeń „w” ma w przypadku zda-
rzenia określonego jako
Right-Control-Button-1
(zgadłeś, czy-
telniku, to jest konstruowane przeciążonym operatorem „-”)
wywołać funkcję
clicked _ button
, podając jej jako argument
to, co jest opisane znacznikiem
ea _ winid
. Mówiąc najbardziej
na skróty, zdarzeniu
Button
odpowiada struktura argumentów
ButtonPressEvent
, pochodna
BasicEvent
, i ta ostatnia zawiera
pole o nazwie winid. Przypisanie
ea _ winid
do tego pola pole-
ga na stworzeniu następującej funkcji:
ea_winid_t::type Shell( ea_winid_t, const BasicEvent& args )
{ return args.winid; }
Każdy znacznik ma swój unikalny typ, aby można było wy-
korzystać przeciążanie, i w definicji tego typu zawiera się
odpowiedni typedef, który określa typ danej obsługiwanej
przez znacznik. Ilość znaczników podawanych do
bind _
event
ma być w założeniu dowolna, a sygnatura funkcji han-
dlera jest ściśle związana z ilością znaczników i przypisa-
nymi im typami. Załóżmy, że typem przypisanym
ea _ winid
jest
int
– to oznacza, że funkcja
clicked _ button
musi mieć
sygnaturę:
bool clicked_button( int );
Skojarzenie z Tcl/Tk i komendą
[bind]
jest jak najbardziej za-
mierzone.
Procedura dispatchowania zdarzenia wygląda tak, że
oryginalnie zdarzenie przesyła wszystkie możliwe dane
w odpowiedniej strukturze, takiej jak np. wspomniana
But-
tonPressEvent
(patrz argument typu
EventArgs
w przytoczo-
nym kodzie). Następnie, w zależności od podanych znacz-
ników, z tej struktury funkcja
Shell
wyłuskuje odpowiednie
wartości, podawane następnie do handlera (patrz
Dispat-
chEventInternal
).
Na Listingu 4 zaś ten sam fragment, zapisany w konwencji
variadic templates.
Porównując te Listingi, proszę zwrócić uwagę na koniecz-
ne pośredniki w pierwszej wersji. Przykładowo
HandlerType
jest konieczny, żeby można było mapować listę argumentów
(włącznie z nieużywanymi) na odpowiedni typ boost::function
(np. żeby
HandlerType<int, void, void, void, void, void>
zo-
stał zamieniony na
function1<bool,
int>
). Mając to, można
użyć
HandlerType
z pełną listą typów parametrów. W przeciw-
nym razie musiałaby być powielona cała definicja wzorca kla-
Listing 4.
Fragment kodu zapisany w konwencji variadic
templates
class
BasicEventDispatcher
{
public
:
virtual
bool
Dispatch
(
const
BasicEvent
*
args
)
=
0
;
virtual
~
BasicEventDispatcher
()
{}
}
;
template
<
class
EventArgs
,
typename
...
Types
>
class
EventDispatcher
:
public
BasicEventDispatcher
{
public
:
typedef
function
<
bool
(
typename
Types
::
type
...
)>
handler_t
;
handler_t
handler
;
EventDispatcher
(
handler_t
hnd
):
handler
(
hnd
)
{}
virtual
bool
Dispatch
(
const
BasicEvent
*
basic_args
)
{
const
EventArgs
*
args
=
static_cast
<
const
EventArgs
*>(
basic_args
);
return
DispatchEventInternal
(
args
,
handler
,
Types
()
...
);
}
}
;
template
<
typename
EventArgs
,
typename
Function
,
typename
...
Types
>
bool
DispatchEventInternal
(
const
EventArgs
*
args
,
Function
handler
,
Types
...
types
)
{
return
handler
(
Shell
(
types
,
*
args
)
...
);
}
53
Variadic templates w C++0x
www.sdjournal.org
Software Developer’s Journal 05/2007
sy
EventDispatcher
, bo dla każdej liczby argumentów należa-
łoby użyć innego typu boost::function.
Kolejnym pośrednikiem jest
DispatchEventInternal
. Nie
usunąłem go z podanego przykładu, ale nie dlatego, że-
by to było niemożliwe (pozostawiam to jako ćwiczenie dla
czytelnika), tylko dlatego, że dzięki temu przykład lepiej
ilustruje sposoby konstruowania wyrażeń z użyciem argu-
ment pack.
Proszę tutaj z tego właśnie względu zwrócić szczególną
uwagę na pozycję, w której zapisano wielokropek i wyobra-
zić sobie, co dokładnie zostanie powielone. Jak widać, powie-
leniu ulega całość wyrażenia od jego początku, a wielokropek
umieszczamy na jego końcu – tam, gdzie powinien zostać po-
stawiony pierwszy przecinek.
Jako kolejne ćwiczenie proponuję napisać funkcję, która
będzie przyjmowała coś w rodzaju „argumentów ze znaczni-
kami”. To znaczy, będzie przyjmowała zawsze parzystą liczbę
argumentów, gdzie pierwszy z pary będzie symboliczną na-
zwą znacznika, a drugi argumentem funkcji odpowiadającym
temu znacznikowi. Przykładowe wywołanie:
fn( FN_COLOR, Red, FN_HEIGHT, 20 );
Istniejące dodatki składniowe
Z ostatniej wersji propozycji variadic templates, następujące
składnie związane z tym ficzerem mają być dodane do C++0x
– poza rozwijaniem wyrażenia w argumenty funkcji:
• pobieranie długości listy wielokrotnych argumentów – tu
używa się składni
sizeof...(Args)
. Poprzednia składnia
si-
zeof (Args...)
była cokolwiek niejednoznaczna
• rozwijanie listy inicjalizacyjnej, na przykładzie
boost::array
template <typename... Types>
void f( Types... args )
{
boost::array<boost::any, sizeof...(Types)> = { args... };
}
• Rozwijanie klas bazowych w dziedziczeniu (i przy okazji
inicjalizacja każdej z nich w konstruktorze):
template <typename... Bases>
class SomeClass: public Bases...
{
SomeClass( const Bases&... bases ): Bases( bases )... {}
};
• Rozwijanie specyfikacji wyjątków
template <typename Exceptions...>
void fn() throw( Exceptions... ) {}
Zastosowania
Autorzy koncepcji variadic templates nie poprzestali na samej
propozycji wzbogacenia języka C++ o określoną właściwość, ale
zaproponowali również nową wersję odpowiednich elementów
biblioteki standardowej, w tym również elementów, które aktual-
nie są proponowane do TR1. Jedną z najważniejszych jest tuple,
znana już dziś jako boost::tuple („generalizacja
std::pair
”) i za-
proponowana do
TR1
. Przystosowanie do tego ficzera tr1::func-
tion i tr1::bind jest już i tak oczywiste. Do
tr1::bind
jednak przy-
dałby się moim zdaniem jeszcze jeden dodatkowy ficzer – tak,
jak ma np.
_ 1
,
_ 2
, żeby miał jeszcze coś w rodzaju
_ N
, oznacza-
jący, że ma przekazać oryginalnej funkcji wszystkie argumen-
ty, które dostanie binder przy wywołaniu. W propozycji znajdują
się również elementy związane z koncepcjami. Co ciekawe, jest
możliwa również konstrukcja:
template<class... Args1, class... Args2>
chociaż tylko w przypadku funkcji. Ale to wystarczy, żeby np.
wznowić dyskusję na temat ścisłych wyjątków w C++. Mając
to rozszerzenie, oraz możliwość opcjonalnego specyfikowa-
nia określonych właściwości języka (składnia do tego została
zaproponowana przy „Garbage Collection”) można by się po-
kusić o opcjonalne ścisłe wyjątki w C++ bez stwarzania pro-
blemów z funkcjami typu std::find_if. Ścisłe, to znaczy takie,
w których niepowodzenie zgłaszane przez std::unexpected
byłoby wykrywane już podczas kompilacji.
Podsumowanie
Ficzer variadic templates na pewno pomoże rozwiązać wiele pro-
blemów, z którymi borykają się wszelkie nowoczesne bibliote-
ki używające zaawansowanych właściwości C++. W tym arty-
kule przedstawiłem, mam nadzieję, wszystkie sprawy związane
z variadic templates i ich konsekwencją dla języka C++ oraz je-
go bibliotek. Jest to o tyle ważna właściwość, że ma dużą szan-
sę przyczynić się do zmniejszenia niepotrzebnego, nadmiarowe-
go kodu i uczynić przez to kod łatwiejszym do zarządzania. Nie
obyło się oczywiście bez odpowiedniego skomplikowania języka
– ale ja osobiście zawsze twierdziłem, że zwiększanie możliwo-
ści języka musi iść w parze ze zwiększaniem jego komplikacji.
Ten ficzer oraz projekt koncepcji, choć są wciąż w fazie
opracowywania propozycji zmiany do standardu, doczekały
się już publicznie dostępnych próbnych implementacji (łatę na
variadic templates do gcc 4.1.1 można ściągnąć ze „strony do-
mowej ficzera” – patrz ramka W Sieci). Jest to nowość w do-
tychczasowym procesie standaryzacji C++ i wnosi pewien po-
wiew optymizmu. Byłoby bardzo miło, gdyby i inne propozy-
cje do standardu doczekały się takich właśnie próbnych im-
plementacji – moim zdaniem następne w kolejce jest auto/
decltype. Niewykluczone też, że przy uzyskaniu odpowiedniej
stabilności zostaną one dodane również do oficjalnych dystry-
bucji gcc, oczywiście jako eksperymentalne ficzery C++. Pyta-
łem zresztą Douga o to i powiedział, że na razie są problemy
z licencją stworzonego przez niego kodu (właścicielem jest
wciąż Indiana University) – ale ich prawnicy pracują nad tym.
Oczywiście, ten ficzer nie jest jeszcze gotowy i wiele mo-
że się w nim zmienić. Zachęcam jednak do zaopatrzenia się
w wersję gcc z tym dodatkiem (w celach edukacyjnych) oraz
przesyłania ewentualnych uwag Douglasowi. Może i Ty przy-
czynisz się do ulepszenia standardu C++... n
W Sieci
• http://www.open-std.org/jtc1/sc22/wg21/ – strona domowa ko-
mitetu standaryzacyjnego języka C++,
• http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/
n2142.html – wykaz aktualnych zagadnień opracowywanych
dla C++0x,
• http://www.osl.iu.edu/~dgregor/cpp/variadic-templates.html –
strona domowa ficzera,
• http://www.boost.org/ – biblioteki boost, w tym tuple, function,
bind i signal.