N dimenziós tömb
Ez itt egy tömb:
Vector<double> v(12);
v[10] = 3.14;
Ez pedig egy mátrix (amelyben a szögletes zárójel operátor helyett a kerek zárójel operátort használjuk indexelésre, mert annak bármennyi paramétere lehet):
Matrix<double> m(3, 4);
m(1, 2) = 3.14;
Általánosítva, egy N dimenziós tömböt így képzelhetnénk el:
Array<double, 5> a(3, 5, 6, 4, 2); // 3×5×6×4×2-es tömb
a(1, 3, 4, 2, 0) = 3.14;
Az első sablonparaméter a tárolt adatok típusa, a második pedig a dimenziók száma. A konstruktorban adjuk meg a tömb méreteit: pontosan annyi darab egész szám kell legyen, ahány dimenzió van. Az indexeléshez használt függvényhívó operátor szintén pontosan annyi egész számot vár paraméterként, ahány dimenzió van.
Készítsd el az Array
osztályt! Ügyelj arra, hogy a fordítási időben kideríthető hiba (helytelen argumentumszám vagy -típus) fordítási hibát eredményezzen!
Megoldás
A megoldás két helyen is C++17 elemet használ, folding expression-t: (... && B)
és (1 * ... * sizes)
. De ezeket nagyon könnyű lenne megírni rekurzívan is.
#include <iostream>
#include <iomanip>
#include <type_traits>
#include <vector>
#include <array>
/* And<true, false, true, ...>::value */
template <bool ... B>
class And : public std::bool_constant<(... && B)> {};
/* AllInt<size_t, int, long>::value */
template <typename... TS>
class AllInt : public And<std::is_convertible<TS, size_t>::value...> {};
template <typename T, size_t DIM>
class Array {
private:
/* ez tárolja, hogy melyik irányban mekkora méretű */
std::array<size_t, DIM> sizes;
/* ez a tényleges adattömb, leképezve 1 dimenzióra */
std::vector<T> data;
public:
template <typename... ARGS,
typename = typename std::enable_if<AllInt<ARGS...>::value>::type,
typename = typename std::enable_if<sizeof...(ARGS)==DIM>::type>
Array(ARGS ... sizes)
: sizes{size_t(sizes)...}
, data((1 * ... * sizes))
{
}
template <typename... ARGS,
typename = typename std::enable_if<AllInt<ARGS...>::value>::type,
typename = typename std::enable_if<sizeof...(ARGS)==DIM>::type>
T & operator() (ARGS ... indices_)
{
std::array<size_t, DIM> indices = { size_t(indices_) ... };
/* ijesztően néz ki, hogy minden indexelésnél ezt csináljuk, de
* megijedés helyett érdemes megnézni a lefordított, optimalizált kódot! */
size_t idx = indices[0];
for (size_t i = 1; i < DIM; ++i)
idx = idx*sizes[i-1] + indices[i];
return data[idx];
}
};
int main() {
Array<double, 2> a(3, 4);
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 4; ++j)
a(i, j) = (i+1)*(j+1);
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j)
std::cout << std::setw(10) << a(i, j);
std::cout << std::endl;
}
}
Saját std::ref
Írd meg az std::ref-et! Emlékeztetőül: ez arra jó, hogy létrehozzon egy funktort, amely egy másik funktorra hivatkozik, de referencia szerint. Így a létrehozott funktor akárhányszor másolható, mindig az eredeti fog dolgozni. Ahogy az std::ref is működik:
class IntPrinter {
private:
int count = 0;
public:
void operator() (int i) {
std::cout << ++count << ". " << i << std::endl;
}
};
int arr1[] = { 5, 9, 2, 4 };
int arr2[] = { 5, 8, 4, 7, 9 };
IntPrinter p;
std::for_each(std::begin(arr1), std::end(arr1), std::ref(p)); /* 1. 2. 3. 4. */
std::for_each(std::begin(arr2), std::end(arr2), std::ref(p)); /* 5. 6. 7. 8. 9. */
Figyelj arra, hogy a becsomagolt függvényobjektumnak tetszőlegesen sok(féle) függvényhívó operátora lehet, és ezeknek típushelyesen kell továbbadni a paramétereket! (Az std::ref() egyébként nem osztály, hanem egy gyártófüggvény.)
Megoldás
/* Ez itt a referencia objektum */
template <typename FUNCTOR>
class RefProxy {
private:
/* Amely a függvényobjektumot referencia szerint tartalmazza */
FUNCTOR &f;
public:
/* A konstruktora eltárolja a referenciát */
RefProxy(FUNCTOR & f) : f(f) {}
/* És ha bármilyen paraméterekkel meghívják */
template <typename... ARGS>
auto operator() (ARGS && ... args)
/* Akkor egy ugyanolyan típusú értékkel tér vissza, mint
* amilyennel a függvényobjektum visszatérne */
-> decltype(f(std::forward<ARGS...>(args...)))
{
/* És továbbpasszolja a paramétereket
* a hivatkozott függvényobjektumnak */
return f(std::forward<ARGS...>(args...));
}
};
/* Ez a függvény hozza létre a RefProxy-t. Tulajdonképpen csak
* azért van, hogy kitalálja a FUNCTOR típusát. */
template <typename FUNCTOR>
auto make_refproxy(FUNCTOR & f) -> RefProxy<FUNCTOR> {
return RefProxy<FUNCTOR>{f};
}
Saját std::function
Írd meg az std::function-t! Ha mindent jól csinálsz, akkor az alábbi kódban az std::function helyére my_function-t írhatsz majd:
#include <functional>
#include <iostream>
#include <cmath>
int main() {
std::function<double(double)> f;
if (!f)
std::cout << "Egyelőre nullptr" << std::endl;
f = sin;
std::cout << sin(2.3) << "==" << f(2.3) << std::endl;
f = [] (double x) { return x*x; };
std::cout << 2.3*2.3 << "==" << f(2.3) << std::endl;
f = std::bind(pow, std::placeholders::_1, 4);
std::cout << pow(2.3, 4) << "==" << f(2.3) << std::endl;
auto f2 = f; /* másolható */
std::cout << pow(2.3, 4) << "==" << f2(2.3) << std::endl;
f = nullptr;
try {
f(2.3);
} catch (std::bad_function_call &e) {
std::cout << "Megint nullptr" << std::endl;
}
}
Tipp
Ehhez az osztályodnak egy olyan operator= kell, amelyik tetszőleges objektumot (függvénypointert, lambdát, ...) át tud venni, és le tud másolni. Ezért a lemásolt objektumot be kell majd csomagolnod egy osztályba, aminek típusa a lemásolt objektumtól függ. Hogy erre lehessen hivatkozni, a különféle elképzelhető csomagoló osztályokhoz szükség van egy közös ősosztályra.
Figyelj arra, hogyan adod át a becsomagolt függvénynek a paramétereket! Meg arra is, hogy egy std::function lemásolásakor lemásolódik a bele csomagolt funktort is. Ez azért fontos, mert a funktornak belső állapota lehet. Az std::function ilyen jellegű viselkedését is tudnia kell az osztálynak.
Saját std::function – még egyszer
Előbb oldd meg a fenti feladatot.
Ha megvan, akkor nézd át a kódod, mennyit foglalkoztál memóriakezeléssel. Ha sokat, akkor egy okos pointerre van szükséged. (Melyikre?) Használj ilyet, ügyelve arra, hogy a lehető legkevesebb erőforráskezelő függvényt (másoló konstruktor, destruktor stb.) kelljen megírnod!
Megoldás
Egy std::unique_ptr
kell. A function
objektum másoló konstruktorát ilyenkor is meg kell írni
(mert klónozni kell benne), viszont a mozgató konstruktor és a destruktor lehet default
. Ugyanez a helyzet az értékadó
operátorokkal, amelyek megírhatók egyben, move and swap segítségével.
Mit csinál a függvény?
Mit csinál az alábbi függvény? Az általa visszaadott függvény, amely hívásmódjában kompatibilis az első paramétereként megadott függvénnyel (ugyanolyan paraméterek és visszatérési érték), miben viselkedik másképpen? Írj kódot a működés bemutatására!
template <typename RET, typename... ARGS>
auto memoized_func(RET (*func)(ARGS...)) -> std::function<RET(ARGS...)> {
std::map<std::tuple<ARGS...>, RET> retvals;
auto memoized = [=] (ARGS... args) mutable -> RET {
auto args_tuple = std::make_tuple(args...);
auto found = retvals.find(args_tuple);
if (found != retvals.end())
return found->second;
auto retval = func(args...);
retvals.insert(make_pair(args_tuple, retval));
return retval;
};
return memoized;
}
Extra kérdések: Mikor érdemes ilyet használni? Mikor nem szabad ilyet használni?
Megoldás
Becsomagolja a paraméterként adott függvényt egy másik függvénybe, amely ellenőrzi azt, hogy hívták-e már pontosan azokkal a paraméterekkel, mint amiket most lát. Ha igen, akkor nem értékeli ki újra a függvényt, hanem csak visszatér az eltárolt értékkel. Ha nem, akkor meghívja és eltárolja a visszaadott értékét, hogy legközelebb már ne kelljen.
double sin_with_print(double x) {
std::cout << "sin_with_print(x) called\n";
return sin(x);
}
int main() {
auto msin = memoized_func(sin_with_print);
std::cout << msin(1.0) << std::endl;
std::cout << msin(1.0) << std::endl;
std::cout << msin(2.0) << std::endl;
std::cout << msin(2.0) << std::endl;
std::cout << msin(1.0) << std::endl;
std::cout << msin(1.0) << std::endl;
}
sin_with_print(x) called 0.841471sin(1.0)
először 0.841471 sin_with_print(x) called 0.909297sin(2.0)
először 0.909297 0.841471 0.841471
Akkor érdemes használni, ha a függvény kiértékelése lassú – sokkal lassabb, mint amennyi ideig
a map
-ben keresés tart. Nem szabad használni, ha a meghívott függvénynek mellékhatása
van, mert a mellékhatás csak egyszer fog megtörténni (adott paraméterek mellett mindig az első
híváskor).
PHP list() I. – a könnyebbik fele
A PHP-ban egy sztringben adott, valamilyen szeparátor karakterrel elválasztott adatokat egyetlen egy sorral „ki lehet csomagolni” egy rakat változóba. Például:
list($a, $b) = explode(" ", "3 4.5"); // $a = "3"; $b = "4.5";
Tekintsünk most el a szeparátor karakter megadásától, vegyük azt fixen szóköznek. A feladat egy olyan C++ függvényt írni, amely a fenti nyelvi szerkezethez hasonlóan tetszőlegesen sok, tetszőleges típusú változó értékét ki tudja venni egy sztringből, ráadásul típushelyesen. Például:
int a;
double b;
unpack("3 4.5", a, b); // a = 3; b = 4.5;
Útmutatás: a sztring darabolásához is használhatsz istringstream-et. Használd az egyes típusok >> operátorát! A felbontandó sztring futási időben is keletkezhet, így biztosan nem fordítási időben kell darabolni.
A feladatnak van egy morcosabb második része is.
Megoldás
Klasszikus megoldás:
#include <iostream>
#include <string>
#include <sstream>
void set_vars(std::istringstream &) {
}
template <typename HEAD, typename... TAIL>
void set_vars(std::istringstream & is, HEAD & arg, TAIL &... tail) {
is >> arg;
set_vars(is, tail...);
}
template <typename... ARGS>
void unpack(std::string const & words, ARGS & ... args) {
std::istringstream is(words);
set_vars(is, args...);
}
int main() {
int a;
double b;
unpack("3 4.5", a, b);
std::cout << a << " " << b;
}
Esetleg C++17-ben:
template <typename ... ARGS>
void unpack(std::string const & str, ARGS & ... args) {
std::istringstream is(str);
(is >> ... >> args);
}
Vagy C++11-ben, a swallow{}
-os trükkel:
template <typename ... ARGS>
void unpack(std::string const & str, ARGS & ... args) {
std::istringstream is(str);
using swallow = int[];
swallow{0, (void(is >> args), 0)...};
}
PHP list() II. – a nehezebbik fele
A feladat hasonló, mint az első részben, de most olyan kódot kell írnod, amelynek a szintaktikája már teljesen megegyezik a PHP-s list() nyelvi szerkezetével:
int a;
double b;
list(a, b) = explode("3 4.5");
Útmutatás: indulj ki a list() függvényből! Ennek létre kell hoznia egy referenciákat tartalmazó segédobjektumot, amely a feldarabolt sztringet át tudja venni az értékadó operátorával. Felhasználhatod az std::tuple osztályt is. Vigyázat, ez jóval nehezebb feladat, mint az első fele!
Megoldás
#include <sstream>
#include <string>
#include <iostream>
#include <tuple>
template <typename ... TYPES>
class List {
private:
std::tuple<TYPES & ...> refs;
template <int N>
void unpack(std::istringstream & is) {
if constexpr (N != sizeof...(TYPES)) {
is >> std::get<N>(refs);
unpack<N+1>(is);
}
}
public:
List(TYPES & ... args) : refs(args...) {}
void operator=(std::string s) {
std::istringstream is(s);
unpack<0>(is);
}
};
template <typename ... TYPES>
auto list(TYPES & ... args) {
return List<TYPES & ...>(args...);
}
std::string explode(std::string s) {
return s;
}
int main() {
int a;
double b;
list(a, b) = explode("3 4.5");
}
Python x,y = y,x
Pythonban az alábbi sor megcseréli két változó tartalmát:
x,y = y,x
Ez a trükk C++11-ben is használható. Ha nem is a vessző operátorral, mert az C++-ban teljesen mást jelent, az std::tie() és std::make_tuple() függvények segítségével megoldható:
std::tie(x, y) = std::make_tuple(y, x);
A feladatod: írj tetszőleges kódrészletet, amely szabványos std::pair osztályt használva megoldja a feladatot:
my_func_1(x, y) = my_func_2(y, x);
A trükk az, hogy a függvények becsomagolják a változókat valamilyen segédobjektum(ok)ba, amely(ek) értékadó operátora fogja elvégezni az értékek másolását. Ha jó a megoldásod, akkor ez nem csak cserére használható, hanem bármilyen adatpárok értékadására, pl. a,b = c,d mintájára.
A legnagyobb
Adott az alábbi maximumkeresés:
#include <iostream>
int greatest(int arr[], int size) {
int max = arr[0];
for (int i = 1; i != size; ++i)
if (arr[i] > max)
max = arr[i];
return max;
}
int main() {
int a[] = { 4, 87, 2, 65, 89, 1 };
std::cout << greatest(a, 6) << std::endl;
}
Írd át ezt jobbrekurzív függvénnyé! Az if()
elágazást érdemes előbb egy ?:
operátoros kifejezéssel
helyettesíteni.
Megoldás
int greatest(int arr[], int size, int max) {
if (size == 0)
return max;
else
return greatest(arr+1, size-1, arr[0] > max ? arr[0] : max);
}
int greatest(int arr[], int size) {
return greatest(arr+1, size-1, arr[0]);
}
Utána pedig írd át sablon metaprogrammá, amelyben a Greater
nevű sablon akárhány számot kaphat
sablonparaméterként! A különálló maximumkereső, és a rekurziót indító két függvényből két külön osztály lesz.
Megoldás
#include <iostream>
template <int max, int... numbers>
struct GreatestHelper;
template <int max, int head, int... tail> // 1
struct GreatestHelper<max, head, tail...> {
static constexpr int value = GreatestHelper<(head > max) ? head : max, tail...>::value;
};
template <int max>
struct GreatestHelper<max> { // 2
static constexpr int value = max;
};
template <int head, int... tail>
struct Greatest {
static constexpr int value = GreatestHelper<head, tail...>::value;
};
int main() {
std::cout << Greatest<4, 87, 2, 65, 89, 1>::value << std::endl;
}
A fenti kódban a Greatest
osztály indítja el a rekurziót, a GreatestHelper
osztály
példányosításával. A GreatestHelper
osztály első paramétere, a max
nevű, a rekurzióban használt
gyűjtőparaméter; a megvizsgált számok közül ez tárolja a legnagyobbat. (A rekurzió indulásakor ez a sorozat első elemét
kapja.)
A segédosztálynak két specializációja van. Az 1-gyessel jelöltet használja a fordító akkor, ha legalább két sablonparaméter van (a gyűjtőparaméter és a vizsgálandó számok), a 2-es változatot pedig akkor, ha már nincs vizsgálandó szám. Az eredeti osztálydeklaráció célja csak annyi, hogy megadja a fordító számára a sablonparamétereket, utána már lehet specializálni az osztályt.
Természetesen működne az a módszer is, ahol az általános eset a legalább két paraméterű, az egyetlen specializáció pedig a pontosan két paraméterű. De így tartalmaz a megoldás egy kis kódduplikációt, tehát a fenti megoldás szebb.
template <int max, int head, int... tail>
struct GreatestHelper {
static constexpr int value = GreatestHelper<(head > max) ? head : max, tail...>::value;
};
template <int max, int head>
struct GreatestHelper<max, head> {
static constexpr int value = head > max ? head : max;
};
Mit csinál a sablon?
Mit csinál ez a két osztálysablon? Próbáld ki, hogy példányosítod valamilyen kicsi
egész számmal (papíron vagy jegyzettömbben)! Pl. mivel egyenértékű a gens<4>::type
belső típus?
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
using type = seq<S...>;
};
Megoldás
/* gens<4> -> N = 4, S = üres */
class gens<4> : gens<3, 3> { };
/* gens<3, 3> -> N = 3, S = 3 */
class gens<3, 3> : gens<2, 2, 3> { };
/* gens<2, 2, 3> -> N = 2, S = 2, 3 */
class gens<2, 2, 3> : gens<1, 1, 2, 3> { };
/* gens<1, 1, 2, 3> -> N = 1, S = 1, 2, 3 */
class gens<1, 1, 2, 3> : gens<0, 0, 1, 2, 3> { };
/* gens<0, 0, 1, 2, 3> -> specializáció! nincs N, S = 0, 1, 2, 3 */
class gens<0, 1, 2, 3> {
using type = seq<0, 1, 2, 3>;
};
És ezt a type
belső típust megörökik az előző, meg az azelőtti stb. osztályok,
tehát végül gens<4>::type
= seq<0, 1, 2, 3>
. A
gens<N>
osztály előállítja seq
osztály sablonparamétereként a 0 ... N-1
természetes számokat.
A template metaprogramozás sűrűje
Az std::tuple
osztálysablonnak bármennyi sablonparamétere lehet: az ott megadott típusú adatokat
tudja eltárolni. Pl. std::tuple<int, double>
egy egész és egy valós számot tárol. Ha adott
egy ilyen objektum t
néven, std::get<0>(t)
veszi ki belőle az egész számot,
std::get<1>(t)
pedig a valósat.
Tegyük fel, hogy van egy függvényük, amely épp egy int
és egy double
paramétert vár.
És azt, hogy van egy tuple
-ünk, amelyben egy egész és egy valós szám van. A feladat: meghívni a függvényt
a tuple
-ben tárolt adatokkal. Egészen pontosan, a feladat: írni egy olyan osztályt, amely képes eltárolni
egy tetszőleges típusú (paraméterezésű) függvényre mutató pointert, továbbá a függvény paraméterezésének megfelelő
objektumokat tartalmazó tuple
-t – hogy aztán valamikor azt a függvényt, azokkal a paraméterekkel, meg
lehessen hívni. Így tetszőlegesen eltárolható egy „felparaméterezett” függvény, hogy később, egy tetszőelges időpontban
meg lehessen hívni.
Az alábbi kód ezt a problémát oldja meg. Elemezd a kódot, értsd meg a működését!
#include <tuple>
#include <iostream>
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ using type = seq<S...>; };
double foo(int x, float y, double z) {
return x + y + z;
}
template <typename Ret, typename ...Args>
struct save_it_for_later {
Ret (*func)(Args...);
std::tuple<Args...> params;
template<int ...S>
Ret callFunc(seq<S...>) {
return func(std::get<S>(params) ...);
}
Ret delayed_dispatch() {
return callFunc(typename gens<sizeof...(Args)>::type());
}
};
int main(void) {
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<double, int, float, double> saved = {foo, t};
std::cout << saved.delayed_dispatch() << std::endl;
}
A C++14 tartalmaz ehhez hasonló eszközöket is.
Rekurzió
Sablon metaprogramokban listákat is kellhet tárolnunk: általában erre egy segédosztály sablonparamétereit szoktuk használni. Például:
template <int...>
class Szamok {};
using Hatvanyok = Szamok<1, 2, 4, 8, 16>;
A feladat: írj ehhez hasonló osztályt, amely a sablonparamétereiben adott számok összegét, illetve a számsor hosszát is megadja statikus tagváltozóként! Például:
int main() {
std::cout << SumLen<1,2,3>::value; /* 1+2+3 = 6 */
std::cout << SumLen<1,2,3>::length; /* 3 db szám */
}
Megoldás
template <int... S>
class SumLen;
template <int X, int... XS>
class SumLen<X, XS...> {
private:
using Rest = SumLen<XS...>;
public:
static constexpr int value = X + Rest::value;
static constexpr int length = 1 + Rest::length;
};
template <>
class SumLen<> {
public:
static constexpr int value = 0;
static constexpr int length = 0;
};
Paraméterként
Írd meg úgy az előző programot, hogy a SumLen
osztály
csak a műveleteket végezze, a Szamok
osztály pedig csak a tárolást!
Ebben az esetben a SumLen
paraméterként kell kapja a Szamok
osztályt:
int main() {
std::cout << SumLen<Szamok<1,2,3>>::value;
std::cout << SumLen<Szamok<1,2,3>>::length;
}
Megoldás
A megoldásban a SumLen
osztálynak egy sablonparamétere van csak, egy típus.
Egy Szamok>X, XS...>
paraméterű részleges specializáció lehet az, amelyik
az egyetlen egy sablonparaméterből, a típusból „ki tudja csomagolni” ezeket az egész számokat.
template <int... S>
class Szamok;
template <typename SZAMOK>
class SumLen;
template <int X, int... XS>
class SumLen<Szamok<X, XS...>> {
private:
using Rest = SumLen<Szamok<XS...>>;
public:
static constexpr int value = X + Rest::value;
static constexpr int length = 1 + Rest::length;
};
template <>
class SumLen<Szamok<>> {
public:
static constexpr int value = 0;
static constexpr int length = 0;
};
N-edik típus
Írj sablon osztályt, amelyik átvesz egy egész számot és egy típuslistát sablonparaméterként! Az osztály feladata, hogy a listából kiválassza valamelyik típust; az annyiadik sorszámút, amennyi a megadott egész szám.
Például: NthType<0, int, double char>::Type
-nál ez int
.
NthType<2, int, double, char>
-nál pedig char
.
Megoldás
Lásd a következő két feladatnál.
Típusok listája
Definiálj osztályt, amelynek sablonparamétereit típusnevek tárolására lehet használni!
using MyTypes = TypeList<int, double, char>;
Definiálj metafüggvényt, amellyel a típuslistából lekérdezhető valamelyik típus! (Ez nem teljesen ugyanaz, mint az előző feladatnál; itt nem a típusnevek adják a függvény paramétereit, hanem a típusneveket egy osztály sablonparaméterében tárolod.)
using SecondType = typename NthType<MyTypes, 2>::type;
static_assert(std::is_same<char, SecondType>);
Írj függvényt, amellyel lekérdezhető, hogy a listában szerepel-e egy adott típus!
Megoldás
C++17-ben ez nagyon könnyű folding expression segítségével. A típusokat tartalmazó osztály nélkül:
template <typename T, typename ... LIST>
struct IsTypeInList {
static constexpr bool value = (std::is_same<T, LIST>::value || ...);
};
Klasszikus megoldás rekurzióval:
template <typename T, typename ... LIST>
struct IsTypeInList;
template <typename T>
struct IsTypeInList<T> { /* üres listára */
static constexpr bool value = false;
};
template <typename T, typename HEAD, typename ... TAIL>
struct IsTypeInList<T, HEAD, TAIL...> { /* egy elemű vagy annál hosszabb listára */
/* az első az, vagy a többi közt kell legyen */
static constexpr bool value =
std::is_same<T, HEAD>::value || IsTypeInList<T, TAIL...>::value;
};
Definiálj metafüggvényt, amelyik megadja, hogy a típuslistában csak különféle típusok szerepelnek-e!
constexpr bool b1 = AllTypesDifferent<TypeList<int, double, char>>::value;
static_assert(b1 == true);
constexpr bool b2 = AllTypesDifferent<TypeList<int, double, int>>::value;
static_assert(b2 == false);
Milyen értéket ad a függvény?
Amikor az std::bind
segítségével létrehozunk egy új függvényt, akkor annak visszatérési
értéke megegyezik az eredeti függvényével. Például std::bind(sqrt, 2)
egy nulla paraméterű
függvényt hoz létre, amely mindig az sqrt(2)
értékét adja: double
értékű függvény
jött létre, mert a becsomagolt double sqrt(double)
függvény is ilyet adott.
Írj sablon osztályt, aminek paramétere egy függvény fejléce (call signature), és megadja, hogy milyen
visszatérési értékkel rendelkezik ez a függvény! Például RetType<double(int)>::type
esetén adjon double
-t.
Specializáld az osztályt, hogy működjön függvénypointerrel és std::function
-nel is!
Megoldás
Lásd a következő két feladatnál.
Mi a függvény paramétere?
Adott az alábbi függvény:
void f(int i, double d);
Ha ebből az std::bind
segítségével kreálunk egy másikat, az f2
nevűt:
using namespace std::placeholders;
auto f2 = std::bind(f, _2, _1); // f2(a, b) -> f(b, a)
Akkor f2
egy f2(double, int)
paraméterezéssel hívható függvény lett, mert
az első paraméteréből az f
második paramétere lesz (ami double
), és második
paraméteréből az f
első paramétere (ami int
).
Írj segédosztályt, amelyik egy függvényről képes megmondani, hogy milyen típuső annak a valahányadik
paramétere! Pl. NthArg<0, void(int, double)>::type
legyen int
, mert
a paraméterlistán int
az első adat.
Specializáld az osztályt, hogy működjön függvénypointerre és std::function
típusra is!
Megoldás
#include <iostream>
#include <type_traits>
#include <functional>
/* NthType - N-edik típus kiválasztása egy listából */
namespace {
template <unsigned N, typename... TYPES>
class NthTypeImpl;
template <typename HEAD, typename ... TAIL>
class NthTypeImpl<0, HEAD, TAIL...> {
public:
using type = HEAD;
};
template <unsigned N, typename HEAD, typename ... TAIL>
class NthTypeImpl<N, HEAD, TAIL...> : public NthTypeImpl<N - 1, TAIL...> {};
}
template <unsigned N, typename... TYPES>
using NthType = typename NthTypeImpl<N, TYPES...>::type;
/* RetVal - Mi a függvény visszatérési értéke? */
namespace {
template <typename T>
class RetValImpl;
template <typename RET, typename ... ARGS>
class RetValImpl<RET(ARGS...)> {
public:
using type = RET;
};
template <typename CALLSIG>
class RetValImpl<CALLSIG*> : public RetValImpl<CALLSIG> {};
template <typename CALLSIG>
class RetValImpl<std::function<CALLSIG>> : public RetValImpl<CALLSIG> {};
}
template <typename T>
using RetVal = typename RetValImpl<T>::type;
/* NthArg - Mi a függvény N-edik paramétere? */
namespace {
template <unsigned N, typename T>
class NthArgImpl;
template <unsigned N, typename RET, typename ... ARGS>
class NthArgImpl<N, RET(ARGS...)> {
public:
using type = NthType<N, ARGS...>;
};
template <unsigned N, typename CALLSIG>
class NthArgImpl<N, CALLSIG*> : public NthArgImpl<N, CALLSIG> {};
template <unsigned N, typename CALLSIG>
class NthArgImpl<N, std::function<CALLSIG>> : public NthArgImpl<N, CALLSIG> {};
}
template <unsigned N, typename T>
using NthArg = typename NthArgImpl<N, T>::type;
int main() {
/* is_same_v = C++17. Ha nem akar fordulni, akkor is_same<X, Y>::value. */
static_assert(std::is_same_v<char, NthType<2, int, double, char>>);
static_assert(std::is_same_v<int, RetVal<int()>>);
static_assert(std::is_same_v<int, RetVal<int(*)()>>);
static_assert(std::is_same_v<int, RetVal<std::function<int()>>>);
static_assert(std::is_same_v<double, NthArg<2, void(int, char, double)>>);
}
Saját tuple I.
Írj my_tuple
osztályt, mint az std::tuple
! Ennek tetszőlegesen sok sablonparamétere lehet, és olyan
típusú adatokat tárol, mint amilyeneket itt kapott. Pl. my_tuple>int, char>
egy egész számot és egy karaktert
tartalmaz. Az egyes adatelemeket lehessen a my_get<n>(t)
függvénnyel elérni!
my_tuple<int, char> t(5, 'X');
std::cout << my_get<0>(t); // 5
my_get<1>(t) = 'Y';
A feladat megoldásához rekurzívan kell példányosítania a my_tuple
osztálynak adattagként saját magát: mindig
az egyik adattagot tartalmazva sajátjaként, és a többit rekurzívan.
Ellenőrizd, hogy az előbb megírt osztályod referenciákat is tud-e tárolni!
int a, int b;
my_tuple<int &, int &> t(a, b);
assert(&a == &my_get<0>(t));
assert(&b == &my_get<1>(t));
Saját tuple II.
Írj my_make_tuple
függvényt, amelyik érték szerint eltárolja a neki paraméterként adott adatokat egy
my_tuple
objektumban! Írj my_tie
függvényt, amelyik referencia szerint tárolja el azokat!
Megoldás
#include <iostream>
#include <cassert>
/* Saját tuple I. */
template <typename ...>
class my_tuple;
/* üres tuple, megállítja a rekurziót */
template <>
class my_tuple<> {};
template <typename HEAD, typename ... TAIL>
class my_tuple<HEAD, TAIL...> {
private:
/* az első paraméternek megfelelő adat */
HEAD data;
/* a többi adat egy egyszerűbb tuple-ben */
my_tuple<TAIL...> more_data;
public:
my_tuple() {}
template <typename HEADP, typename ... TAILP>
my_tuple(HEADP && head, TAILP && ... tail)
: data(std::forward<HEADP>(head))
, more_data(std::forward<TAILP>(tail)...) {}
/* erre a my_tuple_get-nek lesz szüksége.
* lehetne valami publikus getter is, de a rekurzív
* példányosítás implementációs kérdés. */
template <size_t, typename ...>
friend class my_tuple_get;
};
/* segédosztály a my_tuple_get függvényhez,
* mert részleges specializációra van szükség */
template <size_t IDX, typename ... TAIL>
class my_tuple_get;
template <size_t IDX, typename HEAD, typename ... TAIL>
struct my_tuple_get<IDX, HEAD, TAIL...> {
static auto & my_get(my_tuple<HEAD, TAIL...> & t) {
return my_tuple_get<IDX-1, TAIL...>::my_get(t.more_data);
}
};
template <typename HEAD, typename ... TAIL>
struct my_tuple_get<0, HEAD, TAIL...> {
public:
static auto & my_get(my_tuple<HEAD, TAIL...> & t) {
return t.data;
}
};
/* a megírandó my_get függvény delegál a my_tuple_get
* osztály statikus tagfüggvényének */
template <size_t IDX, typename ... T>
auto & my_get(my_tuple<T...> & t) {
return my_tuple_get<IDX, T...>::my_get(t);
}
/* Saját tuple II. */
template <typename ... T>
auto my_make_tuple(T ... data) {
return my_tuple<T...>(std::move(data)...);
}
template <typename ... T>
auto my_tie(T &... data) {
return my_tuple<T &...>(data...);
}
int main() {
my_tuple<int, double> t1;
my_tuple<int, double> t2(5, 1.2);
auto t3 = my_make_tuple(5, 1.2);
my_get<0>(t1) = 2;
std::cout << my_get<1>(t2) << std::endl;
std::cout << my_get<1>(t3) << std::endl;
int a = 2, b = 3;
auto t4 = my_tie(a, b);
my_get<1>(t4) = 5;
std::cout << b;
}