background image

38

Inżynieria

oprogramowania

www.sdjournal.org

 Software Developer’s Journal   05/2007

Mechanizm koncepcji w języku C++: 

nowe oblicze szablonów

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
.

background image

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++

�����������

���

������

������

���

�����������������

����������������

�����

�����������������

�����

�����������������

������

��������

���

��

���

background image

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

());

  

 // ...

}

background image

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

<

_

background image

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-

background image

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.

}

background image

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

}

;

background image

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

();

 

}

}

;

background image

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

>;

}

;

background image

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

;

}