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