2007 05 Mechanizm koncepcji w języku C nowe oblicze szablonów [Inzynieria Oprogramowania]


Inżynieria
oprogramowania
Mechanizm koncepcji w języku C++:
nowe oblicze szablonów
Rafał Kocisz
języku C++ można pisać wiele. Przez jed-
nych programistów kochany, przez innych
STLFilt kontra
Oznienawidzony  jednakże, co tu kryć 
komunikaty o błędach w C++
trudno potraktować go obojętnie. C++ postrzegane
nowocześnie to już nie tylko C rozszerzone o obiek-
Nieczytelne błędy pojawiające się przy kompilacji biblio-
towość. To potężne narzędzie do budowania gene- tek generycznych języka C++ mogą przyprawić o prawdzi-
rycznych bibliotek i wysokowydajnych rozwiązań ty- wy ból głowy. Dla Leora Zolmana problem ten musiał być
szczególnie palący, tak że w końcu napisał on w języku
pu DSEL (ang. Domain Specific Embedded Langu-
Perl narzędzie o nazwie STLFilt. Rozwiązanie to działa bar-
ages), w których, dzięki technikom metaprogramo-
dzo prosto: na swoim wejściu pobiera komunikat o błędzie
wania działającym na etapie kompilacji, można budo-
wygenerowany przez kompilator, odsiewa śmieci i na wyj-
wać zaawansowane mechanizmy modelowania abs-
ściu podaje odchudzony komunikat o błędzie. Ze względu
trakcji, nie płacąc wysokiej ceny w postaci opóznień
na bardzo niestabilne warunki wejściowe (kompilatory ewo-
w czasie wykonania programu. Niestety  ceną za
luują cały czas) STLFilt jest nieustannie modyfikowany. Ak-
tę potęgę jest złożoność. Wystarczy spojrzeć w kod
tualnie narzędzie oferuje wsparcie dla następujących kom-
zródłowy dowolnej biblioteki korzystającej ze wspo-
pilatorów: Comeau C++, gcc 2.95.x/3.x, DJGPP, MSVC++
mnianych mechanizmów (chociażby Boost.MPL lub
6/7.x/8.x, Metrowerks CodeWarrior Pro 7/8, Borland C++ /
Boost.Spirit). Przeciętnemu programiście C++ trud- C++Builder, Intel C++ 7/8, EDG Front End (Generic) oraz
Digital Mars C++. STLFilt można pobrać za darmo ze stro-
no zrozumieć zawarty tam kod, a jeszcze trudniej 
ny internetowej firmy DBSoftware (której założycielem jest
tworzyć własne rozwiązania tego rodzaju. Gdzie le-
właśnie Leor Zolman), pod adresem http://www.bdsoft.com/
ży przyczyna? Wydaje się, że niektóre z właściwo-
tools/stlfilt.html.
ści języka przypadkowo okazały się bardziej potężne
niż spodziewali się jego twórcy. Mowa tu oczywiście
o szablonach (ang. templates). Niemalże wszystkie
nowoczesne techniki C++ bazują na skomplikowa- ju rozwiązań jest oczywiście biblioteka STL, wcho-
nych sztuczkach syntaktycznych związanych z sza- dząca zresztą obecnie w skład biblioteki standar-
blonami, odkrywanych sukcesywnie przez ostatnie dowej języka C++. W zasadzie historia z programo-
lata. W niniejszym artykule opisuję mechanizm kon- waniem generycznym zaczęła się dla C++ w mo-
cepcji w C++: rozszerzenie języka, które ma być re- mencie wprowadzenia do tego języka nowego roz-
medium na wspomniane wyżej problemy. Czytelni- szerzenia w postaci szablonów (ang. templates).
ków, którzy pragną poznać nowe oblicze szablonów Mechanizm ten, pozwalający łatwo tworzyć rodzi-
w języku C++ zapraszam do dalszej lektury. ny klas i funkcji sparametryzowanych typami, oka-
zał się przysłowiowym strzałem w dziesiątkę. Z pa-
 Jest super, jest super, radygmatu programowania generycznego wyewo-
więc o co Ci chodzi? luowały inne techniki: programowanie generatyw-
W ciągu ostatnich lat C++ rozwinęło skrzydła, ewo- ne (ang. generative programming) oraz metapro-
luując w kierunku, którego nie przewidywali chyba gramowanie bazujące na szablonach (ang. tem-
nawet jego pierwotni twórcy. Można śmiało stwier- plate metaprogramming). Programiści zaczęli two-
dzić, że to właśnie dzięki C++ programowanie ge- rzyć na bazie szablonów wysokowydajne rozwią-
neryczne trafiło pod strzechy. Ta ważna technika zania klasy DSEL (ang, Domain Specific Embed-
stała się motorem do budowy wysoce efektywnych ded Languages). Wydawałoby się, że lepiej już
i jednocześnie świetnie nadających się do wielo- być nie może. A jednak nie obyło się bez zgrzytów.
krotnego użycia bibliotek. Prekursorem tego rodza- Aż chciałoby się zacytować słowa piosenki zespo-
łu T.Love (patrz: tytuł niniejszego podpunktu). Nie-
stety, potęga i elastyczność, oferowane przez sza-
Autor pracuje na stanowisku Starszego Specjalisty ds.
blony języka C++ okupione zostały wysoką ceną
Oprogramowania w firmie BLStream (http://www.blstre-
w postaci złożoności. W rezultacie, zarówno pro-
am.com) oraz odbywa studia doktoranckie na Wy-
jektowanie jak i implementacja bibliotek bazują-
dziale Informatyki Politechniki Szczecińskiej (http://
cych na wymienionych wcześniej paradygmatach,
www.wi.ps.pl). Centrum zainteresowań zawodowych au-
tora stanowią technologie mobilne, przetwarzanie rów- okazały się zadaniami bardzo trudnymi. Powodów
noległe oraz języki programowania w ujęciu ogólnym. takiego stanu rzeczy wymieniać można by wiele,
Kontakt z autorem: rafal.kocisz@gmail.com
jednakże za główną przyczynę całego zamiesza-
nia można uznać pewną decyzję projektową pod-
38
www.sdjournal.org
Software Developer s Journal 05/2007
Koncepcje w C++
jętą przy wstępnym określaniu specyfikacji szablonów. Otóż sta fizycznie może nie być w stanie wymusić tworzenia in-
kłopot w tym, że wspomniana konstrukcja języka nie posia- stancji w celu zweryfikowania poprawności. Kolejny poważ-
da formalnie zdefiniowanego mechanizmu nakładania ogra- ny problem, to opis błędów generowanych przez kompilator:
niczeń na własne parametry (typy). Ograniczenia takowe zmora programistów pracujących z nowoczesnymi bibliote-
istnieją, aczkolwiek bazują one na pewnych konwencjach, kami C++. Kłopot jest naprawdę ważki  moim zdaniem to
które można zweryfikować dopiero w póznej fazie kompi- właśnie on powoduje, iż wielu początkujących informatyków
lacji, czyli  chciałoby się rzecz  po rybach. Dla porówna- zraża się do języka C++. Wyobrazmy sobie bowiem mło-
nia, w przypadku zwyczajnych klas, sytuacja taka jest abso- dego adepta trudnej sztuki programowania, który próbuje
lutnie niedopuszczalna. Przykładowo, nie możemy wywołać skompilować fragment kodu pokazany na Listingu 3. Listing
na obiekcie metody, która nie jest zdefiniowana w specyfi- 4 przedstawia wynikowy komunikat o błędzie wyprodukowa-
kacji klasy opisującej wspomniany obiekt. Kompilator powia- ny przez kompilator C++ Visual Studio .NET 7, zaś Listing 5
domi nas o tym problemie w bardzo wczesnej fazie swojej pokazuje komunikat zwrócony przez g++.
pracy (Listing 1). W przypadku szablonów, sprawdzanie po- Koszmar, nieprawdaż? Dla wprawnych oczu, błąd jest
prawności wykonywane jest dopiero w momencie tworzenia oczywisty  przekazujemy do funkcji std::sort parę itera-
instancji metody szablonu klasy, bądz funkcji szablonowej. torów, które nie spełniają wymagań narzuconych przez tę
Opisana sytuacja zaprezentowana jest na Listingu 2. Na- funkcję. Problem w tym, że sort pracuje jedynie z iteratora-
leży zauważyć, że gdyby usunąć ostatnią linię w ciele funk- mi o dostępie bezpośrednim (ang. random access), zaś lista
cji main, to przedstawiony fragment kodu skompilowałby się posiada iteratory oferujące dostęp sekwencyjny. Niestety
bez żadnego problemu. W przedstawionym przykładzie bar- pojęcia w rodzaju iterator o dostępie bezpośrednim czy te-
dzo łatwo zlokalizować i usunąć błąd. Niestety, pisząc dużą rator o dostępie sekwencyjnym nie mogą być wyrażone bez-
bibliotekę sprawa zaczyna się komplikować, gdyż programi- pośrednio w kodzie C++. Są to jedynie pewne konwencje
C
C z Klasami
ANSI C Simula Algol 68
C++
Ada ML
C++
The Annotated C++
Reference Manual
Clu
C++98
ANSI/ISO Standard
C++0x
ANSI C
ANSI/ISO Standard
Rysunek 1. Rodowód języka C++
Software Developer s Journal 05/2007 www.sdjournal.org
39
Inżynieria
oprogramowania
opisane w standardzie języka bądz w specyfikacji danej bi-
Listing 2. Wykrywanie błędów przy kompilacji szablonu
blioteki. Zachęcam Czytelników, aby postarali wyobrazić so-
klasy
bie jak wyglądają komunikaty o błędach w bardziej skompli-
kowanych bibliotekach generycznych. Kiedyś próbowałem class foo
pożenić bibliotekę Boost.Lambda z biblioteką Boost.Smart_ {
ptr. W końcu mi się to udało, ale zrozumienie istoty popeł- public:
nianego błędu na podstawie komunikatów kompilatora zaję- void f1() const;
ło mi kilka godzin. Na końcu okazało się, że błąd był dość };
banalny. Jeśli do tego wszystkiego wezmiemy pod uwagę
zjawisko określane potocznie jako magia kompilatorów (ang. template< class Foo >
compiler magic) (tzn. identyczny kod kompiluje się świetnie class bar
przy użyciu narzędzia X i generuje 3 ekrany błędów przy {
próbie kompilacji narzędziem Y) to otrzymamy pełny ob- public:
raz problemu. Powstaje pytanie  dlaczego komunikaty o void f2( const Foo& a_foo )
błędach nie mogą być bardziej przyjazne dla programisty? {
Przyczyna tkwi we wspomnianym wcześniej błędzie pro- a_foo.f1(); // W porządku.
jektowym. Zasada jest prosta: im wcześniej da się wyłapać a_foo.f2(); // W porządku! Nie wiemy na tym etapie
błąd kompilacji, tym bardziej ogólny i kompaktowy będzie // jakie składowe ma typ Foo...
komunikat na jego temat. W przypadku szablonów błędy }
wyłapywane są bardzo pózno i kompilator  chcąc być po- void f3()
mocny  wypisuje wszystko, co na temat danego problemu {
wie. Czytelników, którzy chcieliby dowiedzieć się jak (przy- }
najmniej częściowo) poradzić sobie z problemem nieczytel- };
nych komunikatów o błędach w języku C++ zapraszam do
ramki STLFilt kontra komunikaty o błędach w C++. W kolej- int main()
nym podpunkcie opowiem o grupie ludzi, którzy postanowi- {
li zmienić istniejący  nieciekawy  stan rzeczy, naprawiając bar< foo > b; // W porządku.
opisywany problem definitywnie  u samego zródła. foo f;
b.f3(); // W porządku.
Komitet standaryzacyjny b.f2( f ); // Dopiero tutaj mamy błąd!
nadciąga z odsieczą! }
Problem przedstawiony w poprzednim podpunkcie oka-
zał się na tyle poważny, że za jego rozwiązanie zabrali się
członkowie komitetu standaryzacyjnego języka C++, z sa- ten nazwano koncepcjami (ang. concepts). Gdyby chcieć
mym Bjarne Stroustrupem na czele. Po wstępnych anali- jednym zdaniem scharakteryzować tę ideę, można by napi-
zach problem okazał się znacznie bardziej złożony i wielo- sać, że koncepcje to podzbiór składni języka służący do na-
wymiarowy, niż sądzono na początku. W rezultacie podjęto kładania ograniczeń na typy w szablonach C++. W prakty-
bardzo odważną decyzję: rozszerzenie składni języka. I tak, ce oznacza to, że język będzie rozszerzony o elementy po-
w nowej odsłonie C++ (roboczo oznaczanej jako C++0x lub zwalające określić zbiór wymagań odnośnie zadanego typu.
C++09) otrzymamy nowy mechanizm, który docelowo ma Dzięki temu takie abstrakcje jak iterator o dostępie bezpo-
stanowić remedium na kłopoty z szablonami. Mechanizm średnim będzie można wyrazić bezpośrednio w C++, a nie
 jak dotąd, jedynie w postaci umownej specyfikacji. Z kolei
kompilator mając z góry określony meta-model typu, będzie
Listing 1. Wykrywanie błędów przy kompilacji klasy
w stanie już na etapie odwołania się do szablonu porów-
class foo nać go z przekazanym argumentem. Gwoli ścisłości nale-
{ ży przypomnieć, że podobne zagadnienie (nakładanie ogra-
public: niczeń na typy będące parametrami szablonów) było rozpa-
void f1() const;
};
Listing 3. Drobny błąd
class bar #include
{ #include
public:
void f2( const foo& a_foo ) int main()
{ {
a_foo.f1(); // W porządku. std::list l;
a_foo.f2(); // Błąd we wczesnej fazie kompilacji! std::sort(l.begin(), l.end());
} // ...
}; }
40
www.sdjournal.org
Software Developer s Journal 05/2007
Koncepcje w C++
Listing 4. VC++: komunikat o błędzie
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\ [
include\list(898) : while compiling _Ty=int
class-template member function 'std: ]
:list<_Ty>::_Nodeptr std::list<_Ty>:: C:\Program Files\Microsoft Visual Studio .NET 2003\
_Buynode(void)' Vc7\include\xutility(634) : see
with declaration of 'std::operator`-''
[ Test.cpp(7) : see reference to function template
_Ty=int instantiation 'void std::sort ] list<_Ty>::iterator>(_RanIt,_RanIt)'
Test.cpp(6) : see reference to class template being compiled
instantiation 'std::list<_Ty>' being with
compiled [
with _Ty=int,
[ _RanIt=std::list::iterator
_Ty=int ]
] C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\ include\algorithm(1795) : error
include\xstring(1453) : warning C2784: 'reverse_iterator<_RanIt>::
C4530: C++ exception handler used, difference_type std::operator -(const
but unwind semantics are not enabled. std::reverse_iterator<_RanIt> &,const
Specify /EHsc std::reverse_iterator<_RanIt> &)' :
C:\Program Files\Microsoft Visual Studio .NET 2003\ could not deduce template argument
Vc7\include\xstring(1444) : while for 'const std::reverse_iterator<_
compiling class-template member RanIt> &' from 'std::list<_Ty>::
function 'void std::basic_string<_ iterator'
Elem,_Traits,_Ax>::_Copy(std:: with
basic_string<_Elem,_Traits,_Ax>:: [
size_type,std::basic_string<_Elem,_ _Ty=int
Traits,_Ax>::size_type)' ]
with C:\Program Files\Microsoft Visual Studio .NET 2003\
[ Vc7\include\xutility(634) : see
_Elem=char, declaration of 'std::operator`-''
_Traits=std::char_traits, C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\
_Ax=std::allocator include\algorithm(1795) : error
] C2784: 'reverse_iterator<_RanIt>::
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\ difference_type std::operator -(const
include\stdexcept(39) : see reference std::reverse_iterator<_RanIt> &,const
to class template instantiation 'std: std::reverse_iterator<_RanIt> &)' :
:basic_string<_Elem,_Traits,_Ax>' could not deduce template argument
being compiled for 'const std::reverse_iterator<_
with RanIt> &' from 'std::list<_Ty>::
[ iterator'
_Elem=char, with
_Traits=std::char_traits, [
_Ax=std::allocator _Ty=int
] ]
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\ C:\Program Files\Microsoft Visual Studio .NET 2003\
include\algorithm(1795) : error Vc7\include\xutility(634) : see
C2784: 'reverse_iterator<_RanIt>:: declaration of 'std::operator`-''
difference_type std::operator -(const C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\
std::reverse_iterator<_RanIt> &,const include\algorithm(1795) : error
std::reverse_iterator<_RanIt> &)' : C2784: 'reverse_iterator<_RanIt>::
could not deduce template argument difference_type std::operator -(const
for 'const std::reverse_iterator<_ std::reverse_iterator<_RanIt> &,const
RanIt> &' from 'std::list<_Ty>:: std::reverse_iterator<_RanIt> &)' :
iterator' could not deduce template argument
with for 'const std::reverse_iterator<_
Software Developer s Journal 05/2007 www.sdjournal.org
41
Inżynieria
oprogramowania
Listing 4 cd. VC++: komunikat o błędzie
RanIt> &' from 'std::list<_Ty>:: acceptable to the predefined operator
iterator' with
with [
[ _Ty=int
_Ty=int ]
] C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\
C:\Program Files\Microsoft Visual Studio .NET 2003\ include\algorithm(1795) : error
Vc7\include\xutility(634) : see C2780: 'void std::_Sort(_RanIt,_
declaration of 'std::operator`-'' RanIt,_Diff,_Pr)' : expects 4
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\ arguments - 3 provided
include\algorithm(1795) : error C:\Program Files\Microsoft Visual Studio .NET 2003\
C2676: binary '-' : 'std::list<_Ty>: Vc7\include\algorithm(1913) : see
:iterator' does not define this declaration of 'std::_Sort'
operator or a conversion to a type
trywane już znacznie wcześniej  przy pracach nad pierw- nizmu koncepcji odsyłam do książki Projektowanie i rozwój
szą odsłoną standardu C++. Wtedy jednak, po burzliwych języka C++ autorstwa Bjarne Stroustrupa. W kontekście sy-
dyskusjach, nie zdecydowano się na obranie tej drogi. Czy- tuacji przedstawionej na Listingu 2, korzystając z koncep-
telników zainteresowanych aspektami historycznymi mecha- cji, kompilator potrafiłby już na etapie odwołania się do sza-
Listing 5. g++: komunikat o błędzie
Test.cpp:8:2: warning: no newline at end of file
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h: In function `void std::sort(_RandomAccessIterator, _
RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator]':
Test.cpp:7: instantiated from here
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2553: error: no match for 'operator-' in '__last - __first'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h: In function `void std::__final_insertion_sort(_
RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iter
ator]':
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2554: instantiated from `void std::sort(_RandomAccessIterator,
_RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<
int>]'
Test.cpp:7: instantiated from here
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2197: error: no match for 'operator-' in '__last - __first'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2199: error: no match for 'operator+' in '__first + _S_threshold'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2200: error: no match for 'operator+' in '__first + _S_threshold'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h: In function `void std::__insertion_sort(_RandomAccessIterator,
_RandomAccessIterator) [with _RandomAccessIterator = std::_List_iteratornt>]':
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2203: instantiated from `void std::__final_insertion_sort(_
RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = st
d::_List_iterator]'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2554: instantiated from `void std::sort(_RandomAccessIterator,
_RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<
int>]'
Test.cpp:7: instantiated from here
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2113: error: no match for 'operator+' in '__first + 1'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2203: instantiated from `void std::__final_insertion_sort(_
RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = st
d::_List_iterator]'
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2554: instantiated from `void std::sort(_RandomAccessIterator,
_RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<
int>]'
Test.cpp:7: instantiated from here
/usr/lib/gcc/i686-pc-cygwin/3.4.4/include/c++/bits/stl_algo.h:2119: error: no match for 'operator+' in '__i + 1'
42
www.sdjournal.org
Software Developer s Journal 05/2007
Koncepcje w C++
Listing 6. Szablon funkcji min Listing 8. Definicja koncepcji LessThanComparable.
template concept LessThanComparable< typename T >
const T& min(const T& x, const T& y) {
{ bool operator<(T, T);
return x < y? x : y; };
}
blonu (w instrukcji bar< foo > b;) stwierdzić, że coś jest nie Gdy przyjrzymy się implementacji wspomnianego szablo-
tak. Po prostu mógłby porównać model typu zdefiniowa- nu, to na pierwszy rzut oka widać, że możemy użyć go z każ-
ny w ramach zadanej koncepcji, co pozwoliłoby mu wykryć, dym typem, który ma zdefiniowany operator mniejszości (<)
że zastosowany typ do wspomnianej koncepcji nie pasuje zwracający wartość typu bool. Popatrzmy teraz na Listing 7.
(w tym konkretnym przypadku brakuje składowej funkcji f2()). Umieściłem tam alternatywną implementację szablonu min
W dalszej części artykułu pokażę, jak można tego rodzaju korzystającą z dobrodziejstw koncepcji.
koncepcję zdefiniować. W tym miejscu warto jeszcze przy- W zasadzie szablon wygląda bardzo podobnie. Podstawo-
toczyć cele, jakie postawili sobie członkowie komitetu, pra- wa (i w zasadzie jedyna różnica) polega na tym, że zamiast
cujący nad propozycją wspomnianego rozszerzenia języka: słowa kluczowego typename w liście parametrów szablonu
użyliśmy identyfikatora LessThanComparable. Szybkie spojrze-
" Koncepcje powinny przede wszystkim służyć jako me- nie na Listing 8 powinno rozwiać wszelkie wątpliwości co do
chanizm ułatwiający pisanie generycznego kodu; cel tego identyfikatora.
ten ma być uzyskany poprzez uproszczenie szablonów W ten oto sposób zdefiniowaliśmy naszą pierwszą kon-
i uczynienie ich bezpieczniejszymi (w sensie bezpie- cepcję. Przeanalizujmy kod przedstawiony na wspomnianym
czeństwa typów). W rezultacie użytkownik nie musiałby Listingu. Definicja koncepcji przypomina nieco definicję sza-
stosować skomplikowanych sztuczek aby uzyskać pożą-
dane efekty.
Listing 9. Nowe podejście do starego problemu
" Szablony w swojej nowej odsłonie muszą oferować ten
sam poziom wydajności jak szablony klasyczne, jako że concept Fooable {
efektywność jest jedną z podstawowych miar sukcesu void f1() const;
w przypadku generycznych bibliotek języka C++. void f2() const;
" Mechanizm koncepcji powinien być przede wszystkim na- };
stawiony na wsparcie paradygmatu programowania gene-
rycznego, dzięki czemu C++ będzie mógł nadal domino- class foo
wać w tej dziedzinie. {
" Koncepcje muszą być kompatybilne wstecz, tak aby ist- public:
niejące szablony mogłyby być bez problemu kompilowane void f1() const;
przy pomocy nowych narzędzi. };
W dalszej części artykułu pokażę, jak projektanci nowej odsło- template< Fooable Foo >
ny języka C++ zamierzają spełnić wszystkie te wymagania. class bar
{
Koncepcje w praktyce: prosty przykład public:
Zanim przejdę do omówienia konkretnych właściwości języ- void f2( const Foo& a_foo )
ka związanych z mechanizmem koncepcji, przedstawię pro- {
ste ich zastosowanie. Chciałbym w ten sposób uniknąć oma- a_foo.f1(); // W porządku.
wiania nowych właściwości języka całkowicie na sucho. a_foo.f2(); // Błąd będzie wyłapany właśnie
W dalszej części artykułu przedstawię bardziej zaawanso- // na tym etapie!
wane przykłady zastosowania koncepcji. Na razie zachęcam }
Czytelników do zapoznania się z Listingiem 6. Przedstawiłem void f3()
tam prosty szablon funkcji. {
}
};
Listing 7. Szablon funkcji min: wykorzystanie koncepcji
LessThanComparable
int main()
template< LessThanComparable T > {
const T& min( const T& x, const T& y ) bar< foo > b; // Błąd: kompilator zauważy, że
{ // klasa foo nie pasuje do koncepcji
return x < y? x : y; // Fooable.
} }
Software Developer s Journal 05/2007 www.sdjournal.org
43
Inżynieria
oprogramowania
Listing 10. Nieco bardziej rozbudowana koncepcja Listing 12. Zastosowanie klauzuli where
auto concept Regular< typename T > template< typename T1, typename T2 >
{ where Regular< T1 > &&
T::T(); // domyślny konstruktor Convertible< T1, T2 > &&
T::T( const T& ); // konstruktor kopiujący Convertible< T2, T1 >
T::~T(); // destruktor class my_class
T& operator=( T&, const T& ); // operator przypisania {
bool operator==( const T&, const T& ); // operator // ...
równości };
bool operator!=( const T&, const T& ); // operator
nierówności
void swap( T&, T& ); // zamiana Listingu 10 przedstawiona jest przykładowa, nieco bardziej
}; rozbudowana koncepcja.
Koncepcja ta opisuje rodzinę typów regularnych  czy-
li takich, które możemy konstruować bez parametrów, nisz-
blonu klasy. W oczy rzuca się od razu lista parametrów i de- czyć, kopiować i porównywać. Przedstawiona koncepcja po-
klaracja operatora. Przedstawiona koncepcja reprezentuje siada tylko jeden parametr. Nic nie stoi jednak na przeszko-
dokładnie to, o co nam chodziło: dowolny typ, którego obiek- dzie, aby stworzyć koncepcję obsługującą klika parametrów
ty można porównywać przy pomocy operatora mniejszości. jednocześnie, np.:
Spójrzmy teraz na Listing 9. Pokazałem tam jak można przy
pomocy koncepcji rozwiać problem przedstawiony wcześniej auto concept Convertible< typename T, typename U >
na Listingu 2. {
operator U( const T& );
Koncepcje, a nowe właściwości języka };
Teraz, gdy znamy już ogólną koncepcję mechanizmu kon-
cepcji, możemy bezpiecznie przejść do pełniejszego omó- Koncepcja ta określa ograniczenie zakładające, że typ T mo-
wienia powiązanych z nim właściwości języka C++. A jest że być automatycznie konwertowany do typu U. Aby użyć tej
o czym dyskutować. Aby zdać sobie sprawę ze złożoności koncepcji musimy skorzystać ze specjalnej formy nakładania
idei koncepcji, warto zastanowić się nad pytaniem: co mo- ograniczenia na szablon w postaci klauzuli where. Na Listingu
że być parametrem szablonu. Odpowiedz jest prosta i za- 11 przedstawiłem prosty szablon korzystający ze wspomnia-
wiera się w dwóch słowach: każdy typ  włącznie z typa- nej koncepcji.
mi definiowanymi przez użytkownika, czy nawet innymi sza- Koncepcje posiadające wielokrotne argumenty w połącze-
blonami. Do przekazywania tych ostatnich dedykowana jest niu z klauzulą where, która pozwala nakładać dodatkowe ogra-
nawet specjalna składnia w postaci szablonowych parame- niczenia na te argumenty, są potężnym narzędziem do nakła-
trów szablonu (ang. template template parameters). Teraz dania ograniczeń na typy. Wyobrazmy sobie, że budujemy
wyobrazmy sobie jak złożony musi być mechanizm języka, szablon klasy, który jako parametry będzie przyjmował dwa
którego zadaniem jest definiowanie ograniczeń na dowolne
typy (np. gdyby ktoś chciał napisać szablon, który jako pa-
Listing 13. Dziedziczenie i agregacja koncepcji
rametr przyjmuje inny szablon, zawierający składową klasę
o określonych metodach). Odpowiedz nasuwa się automa- concept InputIterator< typename Iter, typename Value >
tycznie  mechanizm taki będzie cechował się podobną zło- {
żonością jak jego odpowiednik służący do definiowania ty- typename value_type;
pów. I tak jest w istocie. Składnia koncepcji w języku C++ typename reference;
opiera się na pięciu nowych słowach kluczowych: concept, typename pointer;
concept_map, where, axiom, oraz late_check. Pierwsze typename difference_type;
z wymienionych słów kluczowych poznaliśmy już w po- where Regular; // agregacja koncepcji
przednim punkcie niniejszego artykułu. Słowo to służy do where Convertible;
definicji koncepcji: definicji ograniczenia nakładanego na Value operator*(const Iter&); // dereferencja
typ w postaci listy deklaracji wymaganych składników. Na Iter& operator++(Iter&); // pre-inkrementacja
Iter operator++(Iter&, int); // post-inkrementacja
};
Listing 11. Szablon wykorzystujący koncepcję
template< typename U, typename T > concept ForwardIterator< typename Iter, typename Value >
where Convertible< T, U > : InputIterator // dziedziczenie koncepcji
U convert( const T& t ) {
{ // Iterator sekwencyjny jest w sensie syntaktycznym
return t; // identyczny jak operator wejścia.
} };
44
www.sdjournal.org
Software Developer s Journal 05/2007
Koncepcje w C++
równo agregować jak i dziedziczyć. Proste przykład obydwu
Listing 14. Proste odwzorowania koncepcji
wspomnianych technik, wraz z odpowiednimi komentarzami,
concept_map InputIterator< char* > przedstawiłem na Listingu 13.
{ Warto zauważyć, że w ciele koncepcji InputIterator
typedef char value type; umieszczone są dodatkowo wymagania odnośnie powią-
typedef char& reference; zanych typów, które powinny być zdefiniowane w szablonie
typedef char* pointer; spełniającym tą koncepcję.
typedef std::ptrdiff_t difference_type; Reasumując, koncepcje pozwalają w bezpośredni sposób
}; określić interfejs typu, który ma być parametrem szablonu.
Jednakże nie zawsze ten rodzaj definicji da się zastosować.
concept_map InputIterator< int > Spójrzmy na następujący fragment kodu:
{
typedef int value type; char* p = new char[ 10 ];
typedef int reference; // ... tu wypełniamy zawartość tablicy
typedef int* pointer; char* found = std::find( p, p + 10, 'a' );
typedef int difference type; // ...
int operator*(int x) { return x; }
}; Traktujemy tutaj typ char* jako iterator przekazywany do stan-
dardowej funkcji std::find. Zakładając, że wspomniana funkcja
zaimplementowana jest w postaci szablonu ograniczonego za
typy, przy czym typu te muszą dać się w obie strony konwer- pomocą koncepcji, to na przekazywane do niej iteratory nało-
tować, zaś pierwszy typ powinien być regularny. Ograniczenie żone są odpowiednie wymagania (InputIterator). Jeśli spoj-
tego rodzaju możemy zrealizować stosując kod przedstawio- rzymy ponownie na Listing 13 to przekonamy się, iż koncep-
ny na Listingu 12. cja InputIterator żąda od docelowego typu posiadania typów
Proste, nieprawdaż? We wspomnianym przykładzie warto zagnieżdżonych (np. value _ type). No i w tym momencie po-
zwrócić uwagę na sposób łączenia kilku ograniczeń w klauzuli jawia się kłopot, gdyż typ char* takowych typów nie posiada.
where przy pomocy operatora iloczynu logicznego &&. W cie- I co z tym fantem zrobić? W C++98 problem ten można by
le klauzuli możemy stosować również operator logicznej su- rozwiązać stosując tzw. cechy typów (ang. type traits); w świe-
my (||) i negacji (!). Co ciekawe, ten sam rodzaj klauzuli można cie koncepcji da się uzyskać bardziej eleganckie rozwiązanie.
używać do nakładania ograniczeń na parametry innych kon- Pomogą nam w tym tzw. odwzorowania koncepcji (ang. con-
cepcji. Patrząc na przedstawione powyżej przykłady jasno wi- cept maps), które zaprojektowano właśnie po to, aby adapto-
dać, że korzystanie z omawianego mechanizmu przypomina wać składnię istniejących typów do wymagań określonych
nieco modelowanie typów: z mniejszych koncepcji można bu- koncepcji, bez modyfikacji tych typów. Aby przekonać się jak
dować większe i bardziej złożone. W tej sytuacji aż się prosi to wygląda w praktyce, spójrzmy na Listing 14.
o odpowiednik mechanizmów agregacji i dziedziczenia typów. Pierwsze z dwóch przedstawionych tam odwzorowań
Projektanci koncepcji pomyśleli i o tym: koncepcje można za- koncepcji (definiowanych przy pomocy słowa kluczowego
concept _ map) adaptuje typ char* do interfejsu InputIterator.
Listing 15. Koncepcja stosu i odwzorowanie adaptujące
Listing 16. Przeciążone implementacje funkcji
concept Stack< typename X >
std::advance
{
typename value type; template< InputIterator Iter >
void push(X&, const value type&); void advance( Iter& x, Iter::difference type n )
void pop(X&); {
value type top(const X&); while ( n > 0 ) { ++x; --n; }
bool empty(const X&); }
};
template< BidirectionalIterator Iter >
template< typename T > void advance( Iter& x, Iter::difference type n )
concept map Stack< std::vector< T > > {
{ if (n > 0) while (n > 0) { ++x; --n; }
typedef T value type; else while (n < 0) { --x; ++n; }
void push( std::vector< T >& v, const T& x ) { v.push_ }
back(x); }
void pop( std::vector< T >& v ) { v. pop_back(); } template< RandomAccessIterator Iter >
T top( const std::vector< T >& v ) { return v.back(); } void advance( Iter& x, Iter::difference type n )
bool empty( const std::vector< T >& v ) { return {
v.empty(); } x += n;
}; }
Software Developer s Journal 05/2007 www.sdjournal.org
45
Inżynieria
oprogramowania
Listing 17. Koncepcje iteratorów z biblioteki standardowej języka C++
concept InputIterator };
: CopyConstructible, EqualityComparable,
Assignable concept ForwardIterator
{ co: InputIterator, DefaultConstructible
typename value type = Iter::value type; {
typename reference = Iter::reference; where Convertible;
typename pointer = Iter::pointer; where Convertible;
SignedIntegral difference type = Iter::difference type };
where Arrowable &&
Convertible; concept MutableForwardIterator
reference operator(Iter); : ForwardIterator, OutputIterator
pointer operator! (Iter); {
Iter& operator++(Iter&); where SameType &&
typename postincrement result; SameType;
postincrement result operator++(Iter&, int); };
where Dereferenceable;
W tym przypadku w odwzorowaniu umieszczono jedynie od- na nich obiektów. Regułę tę można obejść jedynie przy pomocy
powiednie definicje typów; reszta wymaganych operacji jest dedykowanego słowa kluczowego late _ check. Koncepcje oka-
dostępna jako część syntaktyki typów wskaznikowych wbu- zują się również wielce przydatne w sytuacji przeciążania sza-
dowanej w język C++. Ciekawostkę stanowi druga część Li- blonów funkcji, w odniesieniu do ich typów szablonowych oraz
stingu, gdzie do koncepcji InputIterator adaptowany jest nie- specjalizacji szablonów klas. Na Listingu 16 zaprezentowana
wskaznikowy typ int. Aby wymaganiom wspomnianej koncep- jest implementacja standardowej funkcji std::advance (przesu-
cji stało się zadość, to oprócz zapewnienia definicji typów, nięcie iteratora o n kroków do przodu), zrealizowana przy pomo-
trzeba jeszcze zdefiniować operator dereferencji. W rezultacie cy przeciążenia bazującego na koncepcjach.
moglibyśmy napisać następujący kod: Jak widać, realizacja powyższego zadania w świecie kon-
cepcji staje się prosta niczym przeciążenie zwyczajnych funk-
std::copy( 1, 10, std::ostream_iterator< int >( std::cout,   cji na bazie ich parametrów. Aby uzyskać podobne rozwiąza-
) ); nie w C++98 trzeba by zastosować mało eleganckie i niewy-
przy czym na wyjściu otrzymalibyśmy sekwencję: godne sztuczki programistyczne (np. idiom SFINAE lub wybór
1 2 3 4 5 6 7 8 9 10 implementacji bazujący na tagach).
Odwzorowania koncepcji można zapisywać również w postaci Koncepcje: konsekwencje
szablonów. W rzeczywistości możliwość ta połączona ze sto- W poprzednim podpunkcie Czytelnicy mieli okazję zapoznać
sowaniem technik adaptacji składni, stanowi prawdziwą moc się mechanizmem koncepcji, postrzeganym przez pryzmat
tej konstrukcji. Na Listingu 15 przedstawiona jest koncepcja nowych właściwości języka C++. W przedstawionym opisie
stosu (struktury danych) oraz odwzorowanie, które adaptu- starałem się wybrać i przedstawić najbardziej istotne cechy
je do tej koncepcji kontener std::vector. Prawda, że eleganc- rozwiązania i z tej racji pominąłem cały szereg specyficznych
kie rozwiązanie? Rozważając temat koncepcji w odniesieniu do zagadnień związanych z tematem (chociażby wyjaśnienie
właściwości języka C++, należy zdać sobie sprawę, że szablo- znaczenia słowa kluczowego axiom). Czytelników zaintere-
ny, które korzystają z omawianego mechanizmu, rządzą się tro- sowanych poznaniem formalnej specyfikacji koncepcji zapra-
chę innymi prawami niż szablony klasyczne. W rzeczywistości szam do ramki W Sieci, gdzie proponuję cały szereg cieka-
można wręcz mówić o nowym rodzaju szablonów (w terminolo- wych materiałów. W niniejszym podpunkcie chciałbym prze-
gii anglojęzycznej określanych jako constrained templates). Sza- analizować temat z innego punktu widzenia. Warto zastano-
blony te mogą zawierać odniesienia do koncepcji w ramach listy wić się jakie konsekwencje pociągnie za sobą wprowadzenie
parametrów (Listing 7), bądz w ciele klauzuli where (Listing 11). mechanizmu koncepcji do języka C++.
Najważniejsza cecha nowych szablonów związana z faktem, iż Na początek rozważmy w jaki sposób koncepcje wpłyną
sprawdzanie poprawności przekazanych do nich typów nastę- na kształt biblioteki standardowej. Przede wszystkim możemy
puje bezpośrednio w momencie tworzenia instancji bazujących spodziewać się nowego nagłówka: . Nagłówek ten
Listing 18: ConceptGcc: komunikat o błędzie
sort_list.cpp: In function  int main() :
sort_list.cpp:8: error: no matching function for call to  sort(std::_List_iterator, std::_List_iterator)
.../stl_algo.h:2835: note: candidates are: void std::sort(_Iter, _Iter) [with _Iter = std::_List_iterator]
sort_list.cpp:8: note: no concept map for requirement  std::MutableRandomAccessIterator >
46
www.sdjournal.org
Software Developer s Journal 05/2007
Koncepcje w C++
będzie zawierał definicje standardowych koncepcji, czyli zapi-
Listing 19. Proste zastosowanie biblioteki BCCL
sane w języku C++ podstawowe wymagania odnośnie typów,
zdefiniowane (zazwyczaj w postaci tabel) w standardzie języ- template
ka. Można więc spodziewać się koncepcji takich jak: Assigna- struct generic_library_class
ble, DefaultConstructible, LessThanComparable, EqualityCom- {
parable itd. Niewątpliwym wyzwaniem dla twórców biblioteki BOOST_CLASS_REQUIRE(T, boost, EqualityComparableConcept);
standardowej będzie zaprojektowanie hierarchii koncepcji ite- // ...
ratorów (na Listingu 17 przedstawiona jest jedna z możliwych };
prób implementacji części tej hierarchii). W tej sytuacji biblio-
teka STL będzie przepisana od nowa i istnieje duża szansa, class foo
że doczekamy się bardziej przyjaznych komunikatów o błę- {
dach. Mając w zanadrzu mechanizm koncepcji, będzie moż- //...
na zaimplementować też algorytmy działające bezpośrednio };
na kolekcjach (a nie  tak jak dotychczas  jedynie ma parach
iteratorów). Co ważne, standardowe koncepcje będą stanowi- int main()
ły solidną bazę do tworzenia własnych bibliotek generycznych {
 prawdopodobnie zdecydowanie bardziej intuicyjnych i prze- generic_library_class glc;
nośnych niż podobne rozwiązania tworzone w C++98. // ...
Koncepcje niewątpliwie wpłyną też na pokrewne paradyg- return 0;
maty programistyczne. Przy projektowaniu klas na bazie stra- }
tegii (ang. policy based class design), koncepcje będzie moż-
na wykorzystać do definiowania interfejsów. Przy tworzeniu
rozwiązań typu DSEL, techniki metaprogramowania oparte- ciągle w fazie Alpha). Fakt ten wynika w dużej mierze z nie-
go na szablonach staną się znacznie prostsze i bardziej prze- stabilności specyfikacji koncepcji, nad którą ciągle prowadzo-
nośne  właśnie dzięki koncepcjom. Kto wie  może pojawią ne są intensywne prace. Do celów edukacyjnych narzędzie
się nowe paradygmaty programowania bazujące na koncep- to jest jednak całkowicie wystarczające. O jego jakości niech
cjach? Jedno już dziś pozostaje pewne: dysponując tym me- zaświadczy komunikat o błędzie wygenerowany przy próbie
chanizmem, C++ będzie w stanie jeszcze bardziej umocnić kompilacji programu pokazanego na Listingu 3. Wspomnia-
swoją pozycję głównego języka wspierającego paradygmat ny komunikat można zobaczyć na Listingu 18. Proponuję aby
programowania generycznego. Czytelnicy porównali sobie zawartość tego Listingu z komuni-
katami wygenerowanymi przez kompilatory VC++ czy g++.
Na bezrybiu Osoby, które chciałyby wdrożyć ideę koncepcji w produk-
Zdaję sobie sprawę, że wielu programistów C++ po przeczy- cyjnych rozwiązaniach opartych na języku C++98, powin-
taniu powyższych akapitów rozmarzy się:  ach, jak dobrze by- ny zapoznać się z biblioteką v (BCCL). Biblioteka ta oferuje
łoby móc skorzystać z dobrodziejstw koncepcji już dziś. . Tym- część funkcjonalności powiązanej z mechanizmem koncep-
czasem trzeba wracać do pracy z istniejącymi kompilatorami cji w postaci akceptowalnej przez istniejące kompilatory C++.
i bibliotekami, które, nawiasem mówiąc, zazwyczaj nie są na- Na Listingu 19 można zobaczyć, jak przy pomocy BCCL moż-
wet w pełni kompatybilne ze specyfikacją standardu C++98. na wymusić proste ograniczenie dla typu przekazywanego ja-
Dla zmartwionych takim stanem rzeczy programistów C++ ko parametr szablonu klasy. Oczywiście możliwości BCCL nie
mam dobrą wiadomość: koncepcje są dostępne już dziś. Oso- umywają się do elastyczności i potęgi koncepcji dostępnych
bom zainteresowanym bezpośrednim zapoznaniem się z nową w postaci rozszerzenia języka, jednakże, jak mówi stare przy-
właściwością języka polecam otwarty kompilator ConceptGCC słowie: na bezrybiu i rak ryba.
(patrz: ramka W Sieci), który pozwala eksperymentować
z koncepcjami w ich pełnej postaci. Kompilator ten niestety nie Podsumowanie
do końca nadaje się do zastosowań komercyjnych (jego imple- W powyższym artykule przedstawiłem podstawowe infor-
mentacja w momencie pisania niniejszego tekstu znajduje się macje na temat mechanizmu koncepcji, którego dołączenie
planuje się w nadchodzącej odsłonie standardu języka C++.
Koncepcje, oferujące zbiór zaawansowanych konstrukcji po-
W Sieci
zwalających modelować ograniczenia typów, będących pa-
rametrami w szablonach klas i funkcji, postrzegane są jako
" http://www.generic-programming.org/  dużo ciekawych infor-
przyszłe remedium na bolączki związane ze złożonością no-
macji na temat programowania generycznego i koncepcji,
woczesnych bibliotek generycznych, pisanych w języku C++.
" http://www.generic-programming.org/software/ConceptGCC/
Celem, który postawiłem sobie przy pisaniu niniejszego ar-
 strona domowa projektu ConceptGCC,
tykułu było przedstawienie mechanizmu koncepcji tak, aby
" http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/
Czytelnik mógł łatwo zrozumieć ideę tego rozwiązania i jed-
n2081.pdf  najświeższa specyfikacja mechanizmu koncepcji,
" http://www.open-std.org/jtc1/sc22/wg21/  strona domowa ko- nocześnie uświadomić sobie konsekwencje stosowania go w
mitetu standaryzacyjnego języka C++, praktyce. Kończąc, chciałbym podkreślić, że niniejszy tekst
" http://www.boost.org/libs/concept_check/concept_check.htm
oparty jest na roboczej specyfikacji mechanizmu koncepcji
 strona domowa biblioteki Boost Concept Check..
i z tej racji nie należy traktować go w kategoriach materiału
referencyjnego. n
Software Developer s Journal 05/2007 www.sdjournal.org
47


Wyszukiwarka

Podobne podstrony:
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
2007 05 P
2007 05 Filtering Vista
Nowe oblicze kolagenu (Postepy KosmetologiiB011)
2007 05 Type Tool Texmacs a Convenient Layout Program for Your Text Documents
2007 05 in a Flash Cross Browser Internet Applications with Openlaszlo
05 mechanika teoretycznaidW47
Beton towarowy nowe oblicze starego materiału na miarę XXI wieku(2)
Beton towarowy nowe oblicze starego materiału na miarę XXI wieku(2)
Nowe oblicze biznesu(1)
Postawy heroiczne (nowe oblicze heroizmu) w literaturze ~2AE
2007 05 Szkoła konstruktorówid 654

więcej podobnych podstron