background image

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 

<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

background image

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

 

print

(

 

const

 

T

&

 

t

 

{

   

std

::

cout

 

<<

 

t

;

}

template

<

 

typename

 

T

typename

... 

Args

>

inline

 

void

 

print

(

 

const

 

T

&

 

t

const

 

Args

&

... 

args

 

{

   

print

(

 

t

 

);

   

print

(

 

args

... 

);

}

background image

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

 

print

(

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

 

)

 

);

}

background image

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

 

)

... 

);

}

background image

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 

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.