2007 05 Variadic templates w C 0x [Programowanie C C ]

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

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

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

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.


Wyszukiwarka

Podobne podstrony:
2007 05 02 odp
2007 05 Mechanizm koncepcji w języku C nowe oblicze szablonów [Inzynieria Oprogramowania]
2007 05 14 praid 25651 Nieznany
SIMR-AN1-EGZ-2007-05-09a-rozw
2007 05 14 prawdopodobie stwo i statystykaid 25652
2007 05 R
2007.05.14 prawdopodobie stwo i statystyka
SIMR-AN1-EGZ-2007-05-09b-rozw
2007.05.23 POS zagadnienia Wroclaw testy
elektro info 2007 05 projekt Rys3 Schemat zasilania RPJ
EDW 2007 05
mat fiz 2007 05 14
Programowanie imprez turystycznych 20.05.2011, Turystyka, Hotelarstwo, programowanie imprez turystyc

więcej podobnych podstron