V minul²ch dφlech naÜeho programßtorskΘho serißlku pro pokroΦilΘ jsme vßs seznßmili s metaprogramovßnφm, Üablonov²mi t°φdami rys∙ a Üablonami v²raz∙. Nynφ si na zßv∞r ukß₧eme, jak tyto techniky kombinovat. Pomocφ metafunkcφ budeme definovat t°φdy rys∙, kterΘ popisujφ obvyklΘ aritmetickΘ konverze a celoΦφselnß rozÜφ°enφ p°i vyhodnocovßnφ v²raz∙.
SyntΘza typu
AritmetickΘ konverze jsou b∞₧nou souΦßstφ vyΦφslovßnφ aritmetick²ch v²raz∙. Nap°φklad kdy₧ sΦφtßme Φφslo typu double a Φφslo typu int, je v²sledek typu double. Tak je psßno ve standardu jazyka a p°ekladaΦe to zvlßdnou. Pokud ale pou₧ijeme nap°φklad vektor parametrizovan² typem dat
template <class T> class Vektor { /*...*/ };
a chceme seΦtenφm vektor∙ typ∙ Vektor<double> a Vektor<int> dostat v²sledek typu Vektor<double>, nem∙₧eme Φekat pomoc od p°ekladaΦe a musφme si to naprogramovat sami.
┌kol je tedy jasn² - nauΦit p°ekladaΦ pou₧φvat aritmetickΘ konverze i pro argumenty Üablon. Jak na to? Jednu z mo₧nostφ jsme u₧ naznaΦili v Φlßnku o t°φdßch rys∙ - pro ka₧dou dvojici typ∙ definovat pomocφ explicitnφ specializace typ v²slednΘ hodnoty. Zopakujme si, jak by to vypadalo:
template <class T1, class T2>
struct Vysledek
{
};
template <>
struct Vysledek<int, double>
{
typedef double TYP;
};
template <>
struct Vysledek<double, int>
{
typedef double TYP;
};
// ... a tak dßl
Je sice pravda, ₧e poΦet t∞chto kombinacφ je koneΦn², nebo¥ koneΦn² je i poΦet vestav∞n²ch typ∙, ale stßle to p°ipomφnß metodu hrubΘ sφly. Te∩ si ukß₧eme, ₧e to lze zvlßdnout elegantn∞ji, a to pomocφ metafunkcφ. Metafunkce bude mφt dva vstupnφ parametry - typy - a bude vracet v²sledn² typ. Mimo jinΘ p∙jde o p∞kn² p°φklad t°φdy rys∙ definovanΘ pomocφ metafunkce.
Jak to vidφ standard
Pro p°ipomenutφ si zde uve∩me pravidla, podle kter²ch se °φdφ aritmetickΘ konverze p°i zpracovßnφ v²raz∙. P°ipome≥me, ₧e nßs zajφmajφ jenom takovΘ situace, kdy parametry operßtor∙ jsou ΦφselnΘ nebo v²ΦtovΘ typy. ZaΦneme u binßrnφch operßtor∙ (standard, Φßst 5.0.9). Nßsledujφcφ pravidla se naz²vajφ obvyklΘ aritmetickΘ konverze:
Jestli₧e jeden z operand∙ je typu long double, pak druh² by m∞l b²t p°eveden na typ long double.
Jinak, jestli₧e jeden z operand∙ je typu double, pak druh² by m∞l b²t p°eveden na typ double.
Jinak, jestli₧e jeden z operand∙ je typu float, pak druh² by m∞l b²t p°eveden na typ float.
Jinak by se m∞ly oba operandy podrobit celoΦφselnΘmu rozÜφ°enφ.
Potom, jestli₧e jeden z operand∙ je typu unsigned long int, pak druh² by m∞l b²t p°eveden na typ unsigned long int.
Jinak, jestli₧e jeden operand je typu long int a druh² typu unsigned int, pak jestli₧e long int m∙₧e reprezentovat vÜechny hodnoty typu unsigned int, operand typu unsigned int by m∞l b²t p°eveden na long int; jinak by oba operandy m∞ly b²t p°evedeny na unsigned long int.
Jinak, jestli₧e jeden z operand∙ je typu long int, pak druh² by m∞l b²t p°eveden na typ long int.
Jinak, jestli₧e jeden z operand∙ je typu unsigned int, pak druh² by m∞l b²t p°eveden na typ unsigned int.
(Dßle u₧ zb²vß jen mo₧nost, ₧e oba operandy jsou typu int.)
CeloΦφselnΘ rozÜφ°enφ je popsßno ve standardu v Φßsti 4.5. ZjednoduÜen∞ °eΦeno: MalΘ celoΦφselnΘ typy (char, signed char, unsigned char, short, unsigned short, wchar_t, bool) a v²ΦtovΘ typy mohou b²t p°evedeny na nejmenÜφ v∞tÜφ celoΦφseln² typ (nejmΘn∞ vÜak int nebo unsigned int), kter² pokr²vß cel² jejich rozsah.
V p°φpad∞ unßrnφch aritmetick²ch operßtor∙ (tj. +, -) se pro celoΦφselnΘ a v²ΦtovΘ typy v₧dy provede celoΦφselnΘ rozÜφ°enφ. Tedy nap°φklad jestli₧e promenna je typu char, pak +promenna nebo -promenna je typu int.
P°ekladaΦ vÜechna tato pravidla znß, ale pokud je chceme pou₧φt i pro ÜablonovΘ argumenty t°φd Φi funkcφ, musφme jim p°ekladaΦ znovu nauΦit - a to prßv∞ pomocφ metak≤du.
CeloΦφselnß rozÜφ°enφ
ZaΦneme s celoΦφseln²m rozÜφ°enφm. Vytvo°φme metafunkci (t°φdu rys∙), kterß bude mφt za parametr typ. Pro v∞tÜinu typ∙ vrßtφ ten sam² typ, ale pro malΘ celoΦφselnΘ typy vrßtφ jejich celoΦφselnΘ rozÜφ°enφ.
// primßrnφ Üablona
template <class T>
struct CelociselneRozsireni
{
typedef T RESULT;
};
// explicitnφ specializace pro
// malΘ celoΦφselnΘ typy
template <>
struct CelociselneRozsireni<char>
{
typedef int RESULT;
};
// ...
A tak podobn∞ pro ostatnφ malΘ celoΦφselnΘ typy - vÜe naleznete na Chip CD 5/01 ve zdrojovΘm souboru aritmeticke_konverze.h. Pokud se v programu objevφ
CelociselneRozsireni<double>::RESULT
nep∙jde o nic jinΘho ne₧ o typ double; zatφmco
CelociselneRozsireni<char>::RESULT
znamenß typ int, nebo¥ se provede rozÜφ°enφ.
Uspo°ßdßnφ typ∙
Zde uva₧ujeme typy int, unsigned int a vÜechny dalÜφ vestav∞nΘ typy s v∞tÜφm rozsahem, nebo¥ spolΘhßme na p°edchozφ metafunkci pro celoΦφselnΘ rozÜφ°enφ. Vid∞li jsme, ₧e aritmetickΘ konverze jsou uspo°ßdßny podle rozsahu dan²ch typ∙. Proto definujeme pomocnou metafunkci (t°φdu rys∙), kterß uspo°ßdß typy podle rozsahu. Nejni₧Üφ po°adφ bude mφt typ int, nejvyÜÜφ pak long double. Primßrnφ Üablona bude tentokrßt prßzdnß - tφm sice znaΦn∞ omezφme poΦet pou₧iteln²ch typ∙ jen na t∞ch pßr vestav∞n²ch, ale pro naÜe ·Φely to zatφm staΦφ.
// primßrnφ Üablona
template <class T>
struct PoradiTypu
{
};
// pro typ int
template <> PoradiTypu<int>
{
static const int Poradi = 1;
};
// pro typ unsigned
template <> PoradiTypu<unsigned>
{
static const int Poradi = 2;
};
// ...
Pro ostatnφ typy se postupuje analogicky. Zbytek naleznete op∞t ve zdrojovΘm souboru aritmeticke_konverze.h.
To byl prvnφ krok. Te∩ musφme popsat, co se stane, kdy₧ se potkajφ dva typy v binßrnφm operßtoru. Pravidlo znßme: vybere se ten s v∞tÜφm rozsahem a druh² typ se na n∞j p°evede (pokud nejsou stejnΘ). Pot°ebujeme tedy implementovat rozhodovacφ schΘma.
Rozhodovacφ schΘma
Pomocφ metafunkce chceme rozhodnout, kter² z typ∙ T1 a T2 mß vetÜφ rozsah. Rozhodovßnφ se °φdφ pravidlem, ₧e v∞tÜφ rozsah mß typ s vyÜÜφm po°adφm. Rozhodovacφ metafunkce nßm odpovφ na otßzku, zda mß typ T1 v∞tÜφ rozsah (je lepÜφ).
template <class T1, class T2>
struct JeTypT1Lepsi
{
static const bool RESULT =
(PoradiTypu<T1>::Poradi >
PoradiTypu<T2>::Poradi);
};
Toto je velmi jednoduchΘ rozhodovacφ schΘma, v n∞m₧ jsme se omezili pouze na vestav∞nΘ aritmetickΘ typy. Dalo by se ovÜem rozÜφ°it i o zpracovßnφ t°φd, nap°. std::complex<T>. Zßjemce odkazujeme na knihovnu Blitz++ (viz infotipy).
U₧ mßme rozhodovacφ metafunkci, ale jeÜt∞ se neumφme postarat o v²b∞r sprßvnΘho typu. NapφÜeme proto dalÜφ pomocnou metafunkci:
template <class T1, class T2, bool VYBER_T1 = true>
struct VyberLepsiTyp
{
typedef T1 RESULT;
};
// ΦßsteΦnß specializace
template <class T1, class T2>
struct VyberLepsiTyp<T1, T2, false>
{
typedef T2 RESULT;
};
Je jist∞ z°ejmΘ, ₧e t°etφm argumentem Üablony bude v²sledek rozhodovacφ metafunkce. Jde v podstat∞ o implementaci ·plnΘho meta-if. V p°φpad∞ spln∞nφ podmφnky (T1 je lepÜφ) se vybere prvnφ typ (T1), v opaΦnΘm p°φpad∞ druh² typ (T2).
Minule jsme vytvo°ili implementaci Üablon v²raz∙, ale bez syntΘzy typu. Nynφ to, jak jsme slφbili, vylepÜφme. P∙vodnφ implementaci Üablon v²raz∙ naleznete na Chip CD v souboru et.cpp a novß, vylepÜenß implementace je v souboru et2.cpp. Zde se zam∞°φme pouze na zm∞ny (v dalÜφm textu zv²razn∞ny). P°edn∞ je t°eba upravit t°φdy rys∙ pro elementßrnφ operace.
template <class TYP>
struct UnarniMinus
{
typedef typename
CelociselneRozsireni<TYP>::RESULT T;
static T apply(TYP x)
{
return -x;
}
};
template <class T1, class T2>
struct BinarniMinus
{
typedef typename
AritmetickaKonverze<T1, T2>::RESULT T;
static T apply(T1 x, T2 y)
{
return x - y;
}
};
Dßle musφme upravit operßtory. Zde uvedeme pouze deklarace, definice naleznete ve zdrojovΘm souboru et2.cpp.
template <class V1, class V2>
Vyraz<BinaryOp<Vyraz<V1>,
Vyraz<V2>, BinarniPlus<
typename Vyraz<V1>::T>,
typename Vyraz<V2>::T> >
operator +(const Vyraz<V1>& v1,
const Vyraz<V2> & v2);
template <class T1, class T2>
Vyraz<BinaryOp<
Promenna<Vektor<T1> >,
Promenna<Vektor<T2> >,
BinarniPlus<T1, T2> > >
operator +(const Vektor<T1>& v1,
const Vektor<T2> & v2);
template <class T, class V>
Vyraz<BinaryOp<
Promenna<Vektor<T> >,
Vyraz<V>,
BinarniPlus<T,
typename Vyraz<V>::T> > >
operator +(const Vektor<T>& v1,
const Vyraz<V> & v2);
PomocnΘ adaptΘry (Vyraz, UnaryOp, BinaryOp) nenφ t°eba m∞nit. Jsou implementovßny natolik obecn∞, ₧e si bez problΘm∙ poradφ i se syntΘzou typu.
A nynφ jednoduch² p°φklad. M∞jme dva vektory
Vektor<int> a;
Vektor<double> b;
JakΘho typu budou slo₧ky vektoru a + b?
P°etφ₧en² operßtor + vrßtφ t°φdu typu
Vyraz<BinaryOp<Promenna<Vektor<int> >,
Promenna<Vektor<double> >,
BinarniPlus<int, double> > >
Odtud m∙₧eme hledan² typ "vypoΦφtat" - zjednoduÜen∞ zapsßno:
Vyraz<...>::T = BinaryOp<...>::T =
= BinarniPlus<int, double>::T =
= AritmetickaKonverze<int, double>::RESULT =
= double
Tak₧e v²sledek je typu Vektor<double>. Pro slo₧it∞jÜφ v²razy je situace obdobnß. Kdy₧ se na to podφvßme z pohledu ÜablonovΘho metaprogramovßnφ, m∙₧eme typ Vyraz<...> pova₧ovat za metastrom - analogii znßmΘ datovΘ struktury. SyntΘza typu je pak rekurzivnφ zpracovßnφ metadat (typ∙) v metastromu, to vÜe v dob∞ p°ekladu.
Zßv∞r?
Tφmto Φlßnkem prozatφm konΦφ nßÜ miniserißl v∞novan² pokroΦil²m programovacφm technikßm pomocφ Üablon. KonΦφ ale definitivn∞? P°iblφ₧ili jsme si koncepty metaprogramovßnφ, t°φd rys∙, Üablon v²raz∙, ale na hodn∞ dalÜφch zajφmav²ch zßle₧itostφ se nedostalo. Nemluvili jsme nap°φklad o typov²ch metaseznamech, t°φdßch politik, multimetodßch a o °ad∞ dalÜφch pozoruhodn²ch programovacφch technik. Mo₧nß se k uveden²m tΘmat∙m jeÜt∞ n∞kdy vrßtφme.
Jaroslav Fran∞k
infotipy
Blitz++:
http://oonumerics.org/blitz
PETE:
http://www.acl.lang.gov/pete
Standard C++:
International standard ISO/IEC 14882, 1998-09-01
Souvisejφcφ Φlßnky:
èablony po Üesti letech, Chip 12/00, str. 192 - 196
D°inu nechte p°ekladaΦi!, Chip 1/01, str. 142 - 145
Specializace trochu jinak, Chip 2/01, str. 142 - 144
èablony v²raz∙ v C++, Chip 4/01, str. 172 - 175
Ukßzky program∙:
Chip CD 5/01, rubrika Chip Plus
-------
Errata
V Φlßnku "èablony v²raz∙ v C++" v minulΘm Φφsle se bohu₧el vyskytlo n∞kolik nedopat°enφ, za kterΘ se vßm autor touto cestou omlouvß:
- V definici operßtoru = (strana 173 uprost°ed) chybφ p°φkaz return *this; - ve zdrojovΘm souboru vÜak je vÜe v po°ßdku.
- P°i poΦeÜ¥ovßnφ jmen prom∞nn²ch (kv∙li v∞tÜφ p°ehlednosti) doÜlo k n∞kolika opomenutφm. Tak₧e UnaryOp a UnarniOp je stejnß t°φda, stejn∞ jako BinaryOp a BinarniOp. Zdrojov² k≤d ale pou₧φvß UnaryOp a BinaryOp.
- Nejv∞tÜφ nep°φjemnostφ je popis "Jak to funguje", kde se v zßv∞ru pomφchala pφsmenka a, b, c.